@pkmn/engine 0.1.0-dev.f3aaee39 → 0.1.0-dev.fc150752

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.f3aaee39",
3
+ "version": "0.1.0-dev.fc150752",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -73,30 +73,30 @@
73
73
  "access": "public"
74
74
  },
75
75
  "dependencies": {
76
- "@pkmn/data": "^0.7.46",
77
- "@pkmn/protocol": "^0.6.12"
76
+ "@pkmn/data": "^0.7.50",
77
+ "@pkmn/protocol": "^0.6.13"
78
78
  },
79
79
  "devDependencies": {
80
- "@pkmn/eslint-config": "^5.0.0",
81
- "@pkmn/img": "^0.2.13",
80
+ "@pkmn/eslint-config": "^5.2.0",
81
+ "@pkmn/img": "^0.2.15",
82
82
  "@pkmn/sets": "^5.0.4",
83
- "@pkmn/sim": "0.7.46",
83
+ "@pkmn/sim": "0.7.50",
84
84
  "@types/html-minifier": "^4.0.2",
85
- "@types/jest": "^29.5.3",
85
+ "@types/jest": "^29.5.4",
86
86
  "@types/minimist": "^1.2.2",
87
87
  "@types/mustache": "^4.2.2",
88
- "@types/node": "^20.4.2",
88
+ "@types/node": "^20.5.9",
89
89
  "@vitest/coverage-c8": "^0.33.0",
90
90
  "binaryen": "^114.0.0",
91
- "eslint": "^8.44.0",
91
+ "eslint": "^8.48.0",
92
92
  "html-minifier": "^4.0.0",
93
93
  "json-stringify-pretty-compact": "3.0.0",
94
94
  "minimist": "^1.2.8",
95
95
  "mustache": "^4.2.0",
96
96
  "source-map-support": "^0.5.21",
97
97
  "trakr": "^0.2.0",
98
- "typescript": "^5.1.6",
99
- "vitest": "^0.33.0"
98
+ "typescript": "^5.2.2",
99
+ "vitest": "^0.34.4"
100
100
  },
101
101
  "scripts": {
102
102
  "lint": "eslint --cache src --ext ts,js && eslint src/bin/*",
@@ -27,17 +27,13 @@ test Player {
27
27
  }
28
28
 
29
29
  /// An identifier for a specific Pokémon in battle.
30
- pub const ID = packed struct {
30
+ pub const ID = packed struct(u8) {
31
31
  /// The one-indexed team slot of the Pokémon
32
32
  id: u3 = 0,
33
33
  /// The Pokémon's trainer.
34
34
  player: Player = .P1,
35
35
  _: u4 = 0,
36
36
 
37
- comptime {
38
- assert(@sizeOf(ID) == 1);
39
- }
40
-
41
37
  /// Converts the identifier into a number.
42
38
  pub inline fn int(self: ID) u4 {
43
39
  return @intCast(@as(u8, @bitCast(self)));
@@ -57,7 +53,7 @@ test ID {
57
53
  }
58
54
 
59
55
  /// A choice made by a player during battle.
60
- pub const Choice = packed struct {
56
+ pub const Choice = packed struct(u8) {
61
57
  /// The choice type.
62
58
  type: Choice.Type = .Pass,
63
59
  /// The choice data:
@@ -73,10 +69,6 @@ pub const Choice = packed struct {
73
69
  Move,
74
70
  Switch,
75
71
  };
76
-
77
- comptime {
78
- assert(@sizeOf(Choice) == 1);
79
- }
80
72
  };
81
73
 
82
74
  test Choice {
@@ -89,7 +81,7 @@ test Choice {
89
81
  }
90
82
 
91
83
  /// The result of the battle - all results other than 'None' should be considered terminal.
92
- pub const Result = packed struct {
84
+ pub const Result = packed struct(u8) {
93
85
  /// The type of result from the perspective of Player 1.
94
86
  /// `Error` is not possible when in Pokémon Showdown compatibility mode.
95
87
  type: Result.Type = .None,
@@ -117,10 +109,6 @@ pub const Result = packed struct {
117
109
  pub const Error: Result = .{ .type = .Error };
118
110
  /// The default result.
119
111
  pub const Default: Result = .{ .p1 = .Move, .p2 = .Move };
120
-
121
- comptime {
122
- assert(@sizeOf(Result) == 1);
123
- }
124
112
  };
125
113
 
126
114
  test Result {
@@ -136,6 +136,8 @@ pub const Damage = enum(u8) {
136
136
  Confusion,
137
137
  LeechSeed,
138
138
  RecoilOf,
139
+
140
+ Spikes,
139
141
  };
140
142
 
141
143
  pub const Status = enum(u8) {
@@ -264,7 +266,15 @@ pub fn Log(comptime Writer: type) type {
264
266
  if (!enabled) return;
265
267
 
266
268
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
267
- try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
269
+ if (@hasField(@TypeOf(pokemon.*), "dvs")) {
270
+ try self.writer.writeAll(&.{
271
+ @intFromEnum(pokemon.species),
272
+ @intFromEnum(pokemon.dvs.gender),
273
+ pokemon.level,
274
+ });
275
+ } else {
276
+ try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
277
+ }
268
278
  try self.writer.writeIntNative(u16, pokemon.hp);
269
279
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
270
280
  try self.writer.writeAll(&.{pokemon.status});
@@ -646,9 +656,13 @@ pub const ByteStream = struct {
646
656
  };
647
657
 
648
658
  pub const Kind = enum { Move, Species, Type, Status };
649
- pub const Formatter = fn (Kind, u8) []const u8;
650
659
 
651
- pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, color: bool) void {
660
+ pub fn format(
661
+ comptime gen: anytype,
662
+ a: []const u8,
663
+ b: ?[]const u8,
664
+ color: bool,
665
+ ) void {
652
666
  print("\n", .{});
653
667
 
654
668
  var i: usize = 0;
@@ -701,17 +715,27 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
701
715
  .Move => {
702
716
  const source = ID.from(@intCast(a[i]));
703
717
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
704
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
718
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
705
719
  const target = ID.from(@intCast(a[i]));
706
720
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
707
721
  const reason: Move = @enumFromInt(a[i]);
708
722
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
709
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
723
+ if (reason == .From) {
724
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
725
+ }
710
726
  },
711
727
  .Switch => {
712
728
  const id = ID.from(@intCast(a[i]));
713
729
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
714
- printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
730
+ printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
731
+ if (@hasDecl(gen, "Gender")) {
732
+ if (a[i] == N(gen.Gender.Unknown)) {
733
+ i += 1;
734
+ } else {
735
+ var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
736
+ printc(", {s}", .{gender}, a, b, &i, 1, color);
737
+ }
738
+ }
715
739
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
716
740
  switch (endian) {
717
741
  .Big => {
@@ -727,7 +751,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
727
751
  printc("/{d}", .{hp}, a, b, &i, 2, color);
728
752
  },
729
753
  }
730
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
754
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
731
755
  },
732
756
  .Cant => {
733
757
  const id = ID.from(@intCast(a[i]));
@@ -735,7 +759,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
735
759
  const reason: Cant = @enumFromInt(a[i]);
736
760
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
737
761
  if (reason == .Disable) {
738
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
762
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
739
763
  }
740
764
  },
741
765
  .Faint,
@@ -775,7 +799,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
775
799
  printc("/{d}", .{hp}, a, b, &i, 2, color);
776
800
  },
777
801
  }
778
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
802
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
779
803
  if (arg == .Damage) {
780
804
  const reason: Damage = @enumFromInt(a[i]);
781
805
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
@@ -795,15 +819,17 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
795
819
  .Status => {
796
820
  const id = ID.from(@intCast(a[i]));
797
821
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
798
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
822
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
799
823
  const reason: Status = @enumFromInt(a[i]);
800
824
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
801
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
825
+ if (reason == .From) {
826
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
827
+ }
802
828
  },
803
829
  .CureStatus => {
804
830
  const id = ID.from(@intCast(a[i]));
805
831
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
806
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
832
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
807
833
  const reason: CureStatus = @enumFromInt(a[i]);
808
834
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
809
835
  },
@@ -826,7 +852,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
826
852
  .Prepare => {
827
853
  const id = ID.from(@intCast(a[i]));
828
854
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
829
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
855
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
830
856
  },
831
857
  .Activate => {
832
858
  const id = ID.from(@intCast(a[i]));
@@ -841,12 +867,12 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
841
867
  if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
842
868
  const types = @as(gen1.Types, @bitCast(a[i]));
843
869
  const args = .{
844
- formatter(.Type, @intFromEnum(types.type1)),
845
- formatter(.Type, @intFromEnum(types.type2)),
870
+ formatter(gen, .Type, @intFromEnum(types.type1)),
871
+ formatter(gen, .Type, @intFromEnum(types.type2)),
846
872
  };
847
873
  printc(" {s}/{s}", args, a, b, &i, 1, color);
848
874
  } else if (reason >= @intFromEnum(Start.Disable)) {
849
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
875
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
850
876
  }
851
877
  },
852
878
  .End => {
@@ -896,7 +922,7 @@ fn printc(
896
922
  }
897
923
 
898
924
  pub fn expectLog(
899
- comptime formatter: Formatter,
925
+ comptime gen: anytype,
900
926
  expected: []const u8,
901
927
  actual: []const u8,
902
928
  offset: usize,
@@ -915,8 +941,8 @@ pub fn expectLog(
915
941
 
916
942
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
917
943
  error.TestExpectedEqual => {
918
- format(formatter, expected, null, color);
919
- format(formatter, actual, expected, color);
944
+ format(gen, expected, null, color);
945
+ format(gen, actual, expected, color);
920
946
  return err;
921
947
  },
922
948
  else => return err,
@@ -956,6 +982,11 @@ const gen1 = struct {
956
982
  pub const helpers = @import("../gen1/helpers.zig");
957
983
  };
958
984
 
985
+ const gen2 = struct {
986
+ pub usingnamespace @import("../gen2/data.zig");
987
+ pub const helpers = @import("../gen2/helpers.zig");
988
+ };
989
+
959
990
  var buf: [gen1.LOGS_SIZE]u8 = undefined;
960
991
  var stream = ByteStream{ .buffer = &buf };
961
992
  var log: FixedLog = .{ .writer = stream.writer() };
@@ -964,15 +995,19 @@ const M = gen1.Move;
964
995
  const S = gen1.Species;
965
996
 
966
997
  fn expectLog1(expected: []const u8, actual: []const u8) !void {
967
- return expectLog(gen1Formatter, expected, actual, 0);
998
+ return expectLog(gen1, expected, actual, 0);
968
999
  }
969
1000
 
970
- fn gen1Formatter(kind: Kind, byte: u8) []const u8 {
1001
+ fn expectLog2(expected: []const u8, actual: []const u8) !void {
1002
+ return expectLog(gen2, expected, actual, 0);
1003
+ }
1004
+
1005
+ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
971
1006
  return switch (kind) {
972
- .Move => @tagName(@as(M, @enumFromInt(byte))),
973
- .Species => @tagName(@as(S, @enumFromInt(byte))),
974
- .Type => @tagName(@as(gen1.Type, @enumFromInt(byte))),
975
- .Status => gen1.Status.name(byte),
1007
+ .Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
1008
+ .Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
1009
+ .Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
1010
+ .Status => gen.Status.name(byte),
976
1011
  };
977
1012
  }
978
1013
 
@@ -1057,6 +1092,25 @@ test "|switch|" {
1057
1092
  };
1058
1093
  try expectLog1(expected, buf[0..9]);
1059
1094
  stream.reset();
1095
+
1096
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1097
+ blissey.level = 91;
1098
+ blissey.hp = 200;
1099
+ blissey.stats.hp = 400;
1100
+ blissey.status = gen2.Status.init(.PAR);
1101
+ try log.switched(p2.ident(3), &blissey);
1102
+ expected = &(.{
1103
+ N(ArgType.Switch),
1104
+ 0b1011,
1105
+ N(gen2.Species.Blissey),
1106
+ N(gen2.Gender.Female),
1107
+ 91,
1108
+ } ++ switch (endian) {
1109
+ .Big => .{ 0, 200, 1, 144, par },
1110
+ .Little => .{ 200, 0, 144, 1, par },
1111
+ });
1112
+ try expectLog2(expected, buf[0..10]);
1113
+ stream.reset();
1060
1114
  }
1061
1115
 
1062
1116
  test "|cant|" {
@@ -79,7 +79,7 @@ pub const Gen12 = extern struct {
79
79
  assert(@sizeOf(Gen12) == 10);
80
80
  }
81
81
 
82
- pub fn percent(p: u8) u8 {
82
+ pub fn percent(comptime p: comptime_int) u8 {
83
83
  return @intCast((@as(u16, p) * 0xFF) / 100);
84
84
  }
85
85
 
@@ -612,7 +612,8 @@ engine, but the following moves have their broken behavior preserved in `-Dshowd
612
612
  - **Roar** / **Whirlwind**: these moves can miss on Pokémon Showdown (and advance the RNG when
613
613
  checking) which is incorrect (these moves should always fail, but don't check accuracy or advance
614
614
  the RNG). More importantly, these moves should *not* cause the tracked last battle damage to be
615
- zeroed, but on Pokémon Showdown they do.
615
+ zeroed, but on Pokémon Showdown they do. These should also `|-fail|...|[still]` instead of doing
616
+ nothing.
616
617
  - **Substitute**: in addition to the [Substitute + Confusion
617
618
  glitch](https://pkmn.cc/bulba-glitch-1#Substitute_.2B_Confusion_glitch) not being implemented
618
619
  correctly (covered earlier), the [Substitute 1/4
@@ -634,6 +635,7 @@ only pedantic UI correctness):
634
635
 
635
636
  - **Rage**: Rage should report the `|-boost|` before the Disable `|-miss|`, not after.
636
637
  - **Haze**: Haze clears the status/volatiles in an incorrect order.
638
+ - **Teleport**: Teleport should `|-fail|...|[still]` instead of doing nothing.
637
639
  - **Twineedle**: the `|-hitcount|` ("Hit 2 times") should come before the `|-status|` message, not
638
640
  after.
639
641
  - **Thrash** / **Petal Dance**: confusion should silently get applied before the `|move|` message,
@@ -29,7 +29,6 @@ const Optional = optional.Optional;
29
29
  const Rational = rational.Rational;
30
30
 
31
31
  const PointerType = util.PointerType;
32
- const FieldType = util.FieldType;
33
32
 
34
33
  const Actions = chance.Actions;
35
34
  const Action = chance.Action;
@@ -115,7 +115,7 @@ test Actions {
115
115
 
116
116
  /// Information about the RNG that was observed during a Generation I battle `update` for a
117
117
  /// single player.
118
- pub const Action = packed struct {
118
+ pub const Action = packed struct(u64) {
119
119
  /// If not 0, the roll to be returned Rolls.damage.
120
120
  damage: u8 = 0,
121
121
 
@@ -156,10 +156,6 @@ pub const Action = packed struct {
156
156
 
157
157
  pub const Field = std.meta.FieldEnum(Action);
158
158
 
159
- comptime {
160
- assert(@sizeOf(Action) == 8);
161
- }
162
-
163
159
  /// Perform a reset by clearing fields which should not persist across updates.
164
160
  pub inline fn reset(self: *Action) void {
165
161
  self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
@@ -239,7 +235,7 @@ pub const Durations = extern struct {
239
235
 
240
236
  /// Observed values for various durations that need to be tracked in order to properly
241
237
  /// deduplicate transitions with a primary key. TODO
242
- pub const Duration = packed struct {
238
+ pub const Duration = packed struct(u16) {
243
239
  /// The number of turns a Pokémon has been observed to be sleeping.
244
240
  sleep: u3 = 0,
245
241
  /// The number of turns a Pokémon has been observed to be confused.
@@ -252,10 +248,6 @@ pub const Duration = packed struct {
252
248
  binding: u3 = 0,
253
249
 
254
250
  pub const Field = std.meta.FieldEnum(Duration);
255
-
256
- comptime {
257
- assert(@sizeOf(Duration) == 2);
258
- }
259
251
  };
260
252
 
261
253
  pub const Commit = enum { hit, miss, binding };