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