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