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