@pkmn/engine 0.1.0-dev.00879b8b

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 (60) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +368 -0
  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 +240 -0
  7. package/build/pkg/data/layout.json +108 -0
  8. package/build/pkg/data/protocol.json +230 -0
  9. package/build/pkg/data.d.ts +91 -0
  10. package/build/pkg/data.js +150 -0
  11. package/build/pkg/data.js.map +1 -0
  12. package/build/pkg/gen1.d.ts +110 -0
  13. package/build/pkg/gen1.js +684 -0
  14. package/build/pkg/gen1.js.map +1 -0
  15. package/build/pkg/index.d.ts +387 -0
  16. package/build/pkg/index.js +122 -0
  17. package/build/pkg/index.js.map +1 -0
  18. package/build/pkg/protocol.d.ts +73 -0
  19. package/build/pkg/protocol.js +473 -0
  20. package/build/pkg/protocol.js.map +1 -0
  21. package/build/test/display.js +400 -0
  22. package/build/test/display.js.map +1 -0
  23. package/build/tools/debug.js +134 -0
  24. package/build/tools/debug.js.map +1 -0
  25. package/build.zig +363 -0
  26. package/package.json +232 -0
  27. package/src/bin/install-pkmn-engine +404 -0
  28. package/src/bin/pkmn-debug +62 -0
  29. package/src/lib/common/battle.zig +17 -0
  30. package/src/lib/common/data.zig +117 -0
  31. package/src/lib/common/debug.zig +74 -0
  32. package/src/lib/common/js.zig +118 -0
  33. package/src/lib/common/optional.zig +67 -0
  34. package/src/lib/common/options.zig +51 -0
  35. package/src/lib/common/protocol.zig +1836 -0
  36. package/src/lib/common/rational.zig +357 -0
  37. package/src/lib/common/rng.zig +243 -0
  38. package/src/lib/common/util.zig +12 -0
  39. package/src/lib/gen1/calc.zig +507 -0
  40. package/src/lib/gen1/chance.zig +1063 -0
  41. package/src/lib/gen1/data/moves.zig +1866 -0
  42. package/src/lib/gen1/data/species.zig +1105 -0
  43. package/src/lib/gen1/data/types.zig +131 -0
  44. package/src/lib/gen1/data.zig +634 -0
  45. package/src/lib/gen1/helpers.zig +866 -0
  46. package/src/lib/gen1/mechanics.zig +3156 -0
  47. package/src/lib/gen2/calc.zig +143 -0
  48. package/src/lib/gen2/chance.zig +1315 -0
  49. package/src/lib/gen2/data/items.zig +230 -0
  50. package/src/lib/gen2/data/moves.zig +2878 -0
  51. package/src/lib/gen2/data/species.zig +1798 -0
  52. package/src/lib/gen2/data/types.zig +226 -0
  53. package/src/lib/gen2/data.zig +463 -0
  54. package/src/lib/gen2/generated.zig +806 -0
  55. package/src/lib/gen2/helpers.zig +249 -0
  56. package/src/lib/gen2/mechanics.zig +3632 -0
  57. package/src/lib/node.zig +131 -0
  58. package/src/lib/pkmn.zig +128 -0
  59. package/src/lib/wasm.zig +13 -0
  60. package/src/test/display/pkmn.html.tmpl +368 -0
@@ -0,0 +1,249 @@
1
+ const std = @import("std");
2
+
3
+ const common = @import("../common/data.zig");
4
+ const options = @import("../common/options.zig");
5
+ const protocol = @import("../common/protocol.zig");
6
+ const rng = @import("../common/rng.zig");
7
+
8
+ const gen1 = @import("../gen1/data.zig");
9
+
10
+ const data = @import("data.zig");
11
+
12
+ const assert = std.debug.assert;
13
+
14
+ const expectEqual = std.testing.expectEqual;
15
+
16
+ const Choice = common.Choice;
17
+
18
+ const showdown = options.showdown;
19
+
20
+ const PSRNG = rng.PSRNG;
21
+
22
+ const DVs = data.DVs;
23
+ const Item = data.Item;
24
+ const Move = data.Move;
25
+ const MoveSlot = data.MoveSlot;
26
+ const Species = data.Species;
27
+ const Stats = data.Stats;
28
+ const Status = data.Status;
29
+
30
+ pub const Options = struct {
31
+ cleric: bool = showdown,
32
+ };
33
+
34
+ pub const Battle = struct {
35
+ pub fn init(
36
+ seed: u64,
37
+ p1: []const Pokemon,
38
+ p2: []const Pokemon,
39
+ ) data.Battle(data.PRNG) {
40
+ var rand = PSRNG.init(seed);
41
+ return .{
42
+ .rng = prng(&rand),
43
+ .sides = .{ Side.init(p1), Side.init(p2) },
44
+ };
45
+ }
46
+
47
+ pub fn fixed(
48
+ comptime rolls: anytype,
49
+ p1: []const Pokemon,
50
+ p2: []const Pokemon,
51
+ ) data.Battle(rng.FixedRNG(2, rolls.len)) {
52
+ return .{
53
+ .rng = .{ .rolls = rolls },
54
+ .sides = .{ Side.init(p1), Side.init(p2) },
55
+ };
56
+ }
57
+
58
+ pub fn random(rand: *PSRNG, opt: Options) data.Battle(data.PRNG) {
59
+ return .{
60
+ .rng = prng(rand),
61
+ .turn = 0,
62
+ .sides = .{ Side.random(rand, opt), Side.random(rand, opt) },
63
+ };
64
+ }
65
+ };
66
+
67
+ pub fn prng(rand: *PSRNG) data.PRNG {
68
+ return .{
69
+ .src = .{
70
+ .seed = if (showdown)
71
+ rand.newSeed()
72
+ else seed: {
73
+ // GLITCH: The initial RNG seed in Gen II link battles is heavily constrained
74
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
75
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
76
+
77
+ // Bytes in seed can only range from 0-252, not 0-255
78
+ const max: u8 = 253;
79
+ // Instructions take multiples of 4 cycles to execute
80
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
81
+ var add: u8 = rand.range(u8, 0, 256);
82
+ var sub: u8 = rand.range(u8, 0, 256);
83
+ var carry: u8 = 0;
84
+ var div: u8 = 0;
85
+
86
+ var i: u8 = 0;
87
+ while (i < seed.len) {
88
+ div = @intCast(timer >> 8);
89
+ // This is effectively @addWithOverflow but with three operands
90
+ const result = @as(u16, add) + @as(u16, div) + @as(u16, carry);
91
+ add = @intCast(result & 255);
92
+ carry = @intFromBool(result > 255);
93
+ timer +%= 44;
94
+ div = @intCast(timer >> 8);
95
+ sub -%= div +% carry;
96
+
97
+ if (sub < max) {
98
+ seed[i] = sub;
99
+ i += 1;
100
+ timer +%= 152;
101
+ carry = 1;
102
+ } else {
103
+ timer +%= 132;
104
+ carry = 0;
105
+ }
106
+ }
107
+ break :seed seed;
108
+ },
109
+ },
110
+ };
111
+ }
112
+
113
+ pub const Side = struct {
114
+ pub fn init(ps: []const Pokemon) data.Side {
115
+ assert(ps.len > 0 and ps.len <= 6);
116
+ var side = data.Side{};
117
+
118
+ for (0..ps.len) |i| {
119
+ side.pokemon[i] = Pokemon.init(ps[i]);
120
+ side.pokemon[i].position = @as(u4, @intCast(i)) + 1;
121
+ }
122
+ return side;
123
+ }
124
+
125
+ pub fn random(rand: *PSRNG, opt: Options) data.Side {
126
+ const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 5 + 1) else 6;
127
+ var side = data.Side{};
128
+
129
+ for (0..n) |i| {
130
+ side.pokemon[i] = Pokemon.random(rand, opt);
131
+ side.pokemon[i].position = @as(u4, @intCast(i)) + 1;
132
+ }
133
+
134
+ return side;
135
+ }
136
+ };
137
+
138
+ pub const EXP = 0xFFFF;
139
+
140
+ pub const Pokemon = struct {
141
+ species: Species,
142
+ moves: []const Move,
143
+ item: Item = .None,
144
+ hp: ?u16 = null,
145
+ status: u8 = 0,
146
+ level: u8 = 100,
147
+ dvs: gen1.DVs = .{},
148
+ stats: Stats(u16) = .{ .hp = EXP, .atk = EXP, .def = EXP, .spe = EXP, .spa = EXP, .spd = EXP },
149
+
150
+ pub fn init(p: Pokemon) data.Pokemon {
151
+ var pokemon = data.Pokemon{};
152
+ pokemon.species = p.species;
153
+ const species = Species.get(p.species);
154
+ inline for (@typeInfo(@TypeOf(pokemon.stats)).Struct.fields) |field| {
155
+ const hp = comptime std.mem.eql(u8, field.name, "hp");
156
+ const spc =
157
+ comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
158
+ @field(pokemon.stats, field.name) = Stats(u16).calc(
159
+ field.name,
160
+ @field(species.stats, field.name),
161
+ if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field.name),
162
+ @field(p.stats, field.name),
163
+ p.level,
164
+ );
165
+ }
166
+ assert(p.moves.len > 0 and p.moves.len <= 4);
167
+ for (p.moves, 0..) |m, j| {
168
+ pokemon.moves[j].id = m;
169
+ // NB: PP can be at most 61 legally (though can overflow to 63)
170
+ pokemon.moves[j].pp = @intCast(@min(Move.pp(m) / 5 * 8, 61));
171
+ }
172
+ pokemon.item = p.item;
173
+ if (p.hp) |hp| {
174
+ pokemon.hp = hp;
175
+ } else {
176
+ pokemon.hp = pokemon.stats.hp;
177
+ }
178
+ pokemon.status = p.status;
179
+ pokemon.types = species.types;
180
+ pokemon.level = p.level;
181
+ pokemon.dvs = DVs.from(p.species, p.dvs);
182
+ return pokemon;
183
+ }
184
+
185
+ pub fn random(rand: *PSRNG, opt: Options) data.Pokemon {
186
+ const s: Species = @enumFromInt(rand.range(u8, 1, Species.size + 1));
187
+ const species = Species.get(s);
188
+ const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
189
+ // Pokémon Showdown does not support most items without in-battle held item effects
190
+ const max_item = @intFromEnum(if (showdown) Item.FlowerMail else Item.TM50);
191
+ const item = if (rand.chance(u8, 1, 10)) 0 else rand.range(u8, 1, max_item + 1);
192
+
193
+ var stats: Stats(u16) = .{};
194
+ const dvs = gen1.DVs.random(rand);
195
+ inline for (@typeInfo(@TypeOf(stats)).Struct.fields) |field| {
196
+ const hp = std.mem.eql(u8, field.name, "hp");
197
+ const spc = std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
198
+ @field(stats, field.name) = Stats(u16).calc(
199
+ field.name,
200
+ @field(species.stats, field.name),
201
+ if (hp) dvs.hp() else if (spc) dvs.spc else @field(dvs, field.name),
202
+ if (rand.chance(u8, 1, 20)) rand.range(u8, 0, 255 + 1) else 255,
203
+ lvl,
204
+ );
205
+ }
206
+
207
+ var ms = [_]MoveSlot{.{}} ** 4;
208
+ const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
209
+ for (0..n) |i| {
210
+ var m: Move = .None;
211
+ sample: while (true) {
212
+ m = @enumFromInt(rand.range(u8, 1, 164 + 1));
213
+ for (0..i) |j| if (ms[j].id == m) continue :sample;
214
+ break;
215
+ }
216
+ const pp_ups: u8 =
217
+ if (!opt.cleric and rand.chance(u8, 1, 10)) rand.range(u2, 0, 2 + 1) else 3;
218
+ // NB: PP can be at most 61 legally (though can overflow to 63)
219
+ const max_pp: u8 = @intCast(Move.pp(m) + pp_ups * @min(Move.pp(m) / 5, 7));
220
+ ms[i] = .{
221
+ .id = m,
222
+ .pp = if (opt.cleric) max_pp else rand.range(u8, 0, max_pp + 1),
223
+ };
224
+ }
225
+ return .{
226
+ .species = s,
227
+ .types = species.types,
228
+ .dvs = DVs.from(s, dvs),
229
+ .level = lvl,
230
+ .stats = stats,
231
+ .hp = if (opt.cleric) stats.hp else rand.range(u16, 0, stats.hp + 1),
232
+ .status = if (!opt.cleric and rand.chance(u8, 1, 6 + 1))
233
+ 0 | (@as(u8, 1) << rand.range(u3, 1, 6 + 1))
234
+ else
235
+ 0,
236
+ .happiness = if (rand.chance(u8, 1, 10 + 1)) rand.range(u8, 0, 255) else 255,
237
+ .item = item,
238
+ .moves = ms,
239
+ };
240
+ }
241
+ };
242
+
243
+ pub fn move(slot: u4) Choice {
244
+ return .{ .type = .Move, .data = slot };
245
+ }
246
+
247
+ pub fn swtch(slot: u4) Choice {
248
+ return .{ .type = .Switch, .data = slot };
249
+ }