@pkmn/engine 0.1.0-dev.8b192003 → 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/README.md +9 -6
- 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 +34 -65
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +15 -2
- package/build/pkg/data/protocol.json +23 -9
- package/build/pkg/data.d.ts +23 -1
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.d.ts +13 -17
- package/build/pkg/gen1.js +6 -10
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +38 -90
- 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 +105 -103
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +35 -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 +254 -77
- package/package.json +50 -134
- package/src/bin/install-pkmn-engine +9 -7
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/common/array.zig +77 -0
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +39 -8
- package/src/lib/common/js.zig +4 -2
- package/src/lib/common/optional.zig +13 -9
- package/src/lib/common/options.zig +4 -0
- package/src/lib/common/protocol.zig +45 -44
- package/src/lib/common/rational.zig +53 -51
- package/src/lib/common/rng.zig +5 -7
- package/src/lib/common/util.zig +50 -1
- package/src/lib/gen1/calc.zig +295 -154
- package/src/lib/gen1/chance.zig +477 -119
- package/src/lib/gen1/data/moves.zig +2 -4
- package/src/lib/gen1/data/species.zig +1 -3
- package/src/lib/gen1/data/types.zig +1 -2
- package/src/lib/gen1/data.zig +35 -38
- package/src/lib/gen1/helpers.zig +17 -27
- package/src/lib/gen1/mechanics.zig +248 -148
- package/src/lib/gen2/calc.zig +37 -27
- package/src/lib/gen2/chance.zig +87 -67
- package/src/lib/gen2/data/items.zig +1 -3
- package/src/lib/gen2/data/moves.zig +2 -4
- package/src/lib/gen2/data/species.zig +1 -3
- package/src/lib/gen2/data/types.zig +1 -2
- package/src/lib/gen2/data.zig +26 -29
- package/src/lib/gen2/generated.zig +18 -23
- package/src/lib/gen2/helpers.zig +9 -18
- package/src/lib/gen2/mechanics.zig +81 -81
- package/src/lib/node.zig +2 -4
- package/src/lib/pkmn.zig +11 -5
- package/src/lib/wasm.zig +36 -1
- 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/test/display/pkmn.html.tmpl +0 -368
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -1,44 +1,43 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const pkmn = @import("../pkmn.zig");
|
|
1
|
+
const DEBUG = @import("../common/debug.zig").print;
|
|
4
2
|
|
|
3
|
+
const chance = @import("chance.zig");
|
|
5
4
|
const common = @import("../common/data.zig");
|
|
6
|
-
const
|
|
5
|
+
const data = @import("data.zig");
|
|
6
|
+
const helpers = @import("helpers.zig");
|
|
7
7
|
const optional = @import("../common/optional.zig");
|
|
8
|
+
const pkmn = @import("../pkmn.zig");
|
|
8
9
|
const protocol = @import("../common/protocol.zig");
|
|
9
10
|
const rational = @import("../common/rational.zig");
|
|
11
|
+
const std = @import("std");
|
|
10
12
|
const util = @import("../common/util.zig");
|
|
11
13
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const helpers = @import("helpers.zig");
|
|
15
|
-
|
|
14
|
+
const Action = chance.Action;
|
|
15
|
+
const Actions = chance.Actions;
|
|
16
16
|
const assert = std.debug.assert;
|
|
17
|
-
|
|
17
|
+
const Chance = chance.Chance;
|
|
18
|
+
const Choice = common.Choice;
|
|
19
|
+
const Confusion = chance.Confusion;
|
|
20
|
+
const Duration = chance.Duration;
|
|
21
|
+
const Durations = chance.Durations;
|
|
22
|
+
const enabled = pkmn.options.calc;
|
|
18
23
|
const expect = std.testing.expect;
|
|
19
24
|
const expectEqual = std.testing.expectEqual;
|
|
20
25
|
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const Player = common.Player;
|
|
25
|
-
const Result = common.Result;
|
|
26
|
-
const Choice = common.Choice;
|
|
27
|
-
|
|
26
|
+
const isPointerTo = util.isPointerTo;
|
|
27
|
+
const Observation = chance.Observation;
|
|
28
28
|
const Optional = optional.Optional;
|
|
29
|
-
|
|
30
|
-
const Rational = rational.Rational;
|
|
31
|
-
|
|
29
|
+
const Player = common.Player;
|
|
32
30
|
const PointerType = util.PointerType;
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const Chance = chance.Chance;
|
|
37
|
-
const Durations = chance.Durations;
|
|
31
|
+
const Rational = rational.Rational;
|
|
32
|
+
const Result = common.Result;
|
|
33
|
+
const Sleeps = chance.Sleeps;
|
|
38
34
|
|
|
39
35
|
const tty = true; // DEBUG
|
|
40
36
|
const summary = false; // DEBUG
|
|
41
37
|
|
|
38
|
+
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
39
|
+
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
40
|
+
|
|
42
41
|
/// Information relevant to damage calculation that occured during a Generation I battle `update`.
|
|
43
42
|
pub const Summaries = extern struct {
|
|
44
43
|
/// Relevant information for Player 1.
|
|
@@ -52,7 +51,7 @@ pub const Summaries = extern struct {
|
|
|
52
51
|
|
|
53
52
|
/// Returns the `Summary` for the given `player`.
|
|
54
53
|
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
|
|
55
|
-
assert(
|
|
54
|
+
assert(isPointerTo(self, Summaries));
|
|
56
55
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
57
56
|
}
|
|
58
57
|
};
|
|
@@ -88,21 +87,11 @@ pub const Summary = extern struct {
|
|
|
88
87
|
}
|
|
89
88
|
};
|
|
90
89
|
|
|
91
|
-
/// TODO
|
|
92
|
-
pub const Overrides = extern struct {
|
|
93
|
-
/// TODO
|
|
94
|
-
actions: Actions = .{},
|
|
95
|
-
|
|
96
|
-
comptime {
|
|
97
|
-
assert(@sizeOf(Overrides) == 16);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
90
|
/// Allows for forcing the value of specific RNG events during a Generation I battle `update` via
|
|
102
91
|
/// `overrides` and tracks `summaries` of information relevant to damage calculation.
|
|
103
92
|
pub const Calc = struct {
|
|
104
93
|
/// Overrides the normal behavior of the RNG during an `update` to force specific outcomes.
|
|
105
|
-
overrides:
|
|
94
|
+
overrides: Actions = .{},
|
|
106
95
|
/// Information relevant to damage calculation.
|
|
107
96
|
summaries: Summaries = .{},
|
|
108
97
|
|
|
@@ -113,16 +102,22 @@ pub const Calc = struct {
|
|
|
113
102
|
) ?std.meta.FieldType(Action, field) {
|
|
114
103
|
if (!enabled) return null;
|
|
115
104
|
|
|
116
|
-
const overrides
|
|
117
|
-
if (player == .P1) self.overrides.actions.p1 else self.overrides.actions.p2;
|
|
118
|
-
const val = @field(overrides, @tagName(field));
|
|
105
|
+
const val = @field(self.overrides.get(player), @tagName(field));
|
|
119
106
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
Enum => val != .None,
|
|
108
|
+
Int => val != 0,
|
|
122
109
|
else => unreachable,
|
|
123
110
|
}) val else null;
|
|
124
111
|
}
|
|
125
112
|
|
|
113
|
+
pub fn confusion(self: *Calc, player: Player) void {
|
|
114
|
+
if (!enabled) return;
|
|
115
|
+
|
|
116
|
+
if (self.overrides.get(player).confusion == .started) {
|
|
117
|
+
self.overrides.get(player).confusion = .None;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
126
121
|
pub fn base(self: *Calc, player: Player, val: u16) void {
|
|
127
122
|
if (!enabled) return;
|
|
128
123
|
|
|
@@ -156,6 +151,10 @@ const Null = struct {
|
|
|
156
151
|
return null;
|
|
157
152
|
}
|
|
158
153
|
|
|
154
|
+
pub fn confusion(self: Null, player: Player) void {
|
|
155
|
+
_ = .{ self, player };
|
|
156
|
+
}
|
|
157
|
+
|
|
159
158
|
pub fn base(self: Null, player: Player, val: u16) void {
|
|
160
159
|
_ = .{ self, player, val };
|
|
161
160
|
}
|
|
@@ -183,7 +182,7 @@ pub const Options = struct {
|
|
|
183
182
|
metronome: bool = false,
|
|
184
183
|
};
|
|
185
184
|
|
|
186
|
-
pub const MAX_FRONTIER =
|
|
185
|
+
pub const MAX_FRONTIER = 81;
|
|
187
186
|
|
|
188
187
|
pub fn transitions(
|
|
189
188
|
battle: anytype,
|
|
@@ -202,18 +201,16 @@ pub fn transitions(
|
|
|
202
201
|
var frontier = std.ArrayList(Actions).init(allocator);
|
|
203
202
|
defer frontier.deinit();
|
|
204
203
|
|
|
204
|
+
const d = options.durations;
|
|
205
|
+
|
|
205
206
|
var opts = pkmn.battle.options(
|
|
206
207
|
protocol.NULL,
|
|
207
|
-
Chance(Rational(u128)){ .probability = .{} },
|
|
208
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = d },
|
|
208
209
|
Calc{},
|
|
209
210
|
);
|
|
210
211
|
|
|
211
212
|
var b = battle;
|
|
212
213
|
_ = try b.update(c1, c2, &opts);
|
|
213
|
-
|
|
214
|
-
var d = options.durations;
|
|
215
|
-
try d.update(&battle, &opts.chance.probability, opts.chance.actions);
|
|
216
|
-
|
|
217
214
|
stats.updates += 1;
|
|
218
215
|
|
|
219
216
|
const p1 = b.side(.P1);
|
|
@@ -231,9 +228,10 @@ pub fn transitions(
|
|
|
231
228
|
var i: usize = 0;
|
|
232
229
|
assert(frontier.items.len == 1);
|
|
233
230
|
while (i < frontier.items.len) : (i += 1) {
|
|
234
|
-
const
|
|
231
|
+
const f = frontier.items[i];
|
|
232
|
+
const saved = stats.saved;
|
|
235
233
|
|
|
236
|
-
try debug(writer,
|
|
234
|
+
try debug(writer, f, .{
|
|
237
235
|
.shape = true,
|
|
238
236
|
.color = i,
|
|
239
237
|
.bold = true,
|
|
@@ -241,33 +239,44 @@ pub fn transitions(
|
|
|
241
239
|
.indent = false,
|
|
242
240
|
});
|
|
243
241
|
|
|
244
|
-
var a: Actions = .{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for (Rolls.
|
|
250
|
-
for (Rolls.
|
|
251
|
-
for (Rolls.
|
|
252
|
-
for (Rolls.
|
|
253
|
-
for (Rolls.
|
|
254
|
-
for (Rolls.
|
|
255
|
-
for (Rolls.
|
|
256
|
-
for (Rolls.
|
|
257
|
-
for (Rolls.
|
|
258
|
-
for (Rolls.
|
|
259
|
-
for (Rolls.
|
|
260
|
-
for (Rolls.
|
|
261
|
-
for (Rolls.
|
|
262
|
-
for (Rolls.
|
|
263
|
-
for (Rolls.
|
|
264
|
-
for (Rolls.
|
|
265
|
-
|
|
266
|
-
|
|
242
|
+
var a: Actions = .{
|
|
243
|
+
.p1 = .{ .metronome = p1_move, .pp = f.p1.pp, .duration = f.p1.duration },
|
|
244
|
+
.p2 = .{ .metronome = p2_move, .pp = f.p2.pp, .duration = f.p2.duration },
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
248
|
+
for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
|
|
249
|
+
for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
|
|
250
|
+
for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
|
|
251
|
+
for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
|
|
252
|
+
for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
|
|
253
|
+
for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
|
|
254
|
+
for (Rolls.confusion(f.p1, d.p1, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
255
|
+
for (Rolls.confusion(f.p2, d.p2, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
256
|
+
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
257
|
+
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
258
|
+
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
259
|
+
for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
260
|
+
for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
|
|
261
|
+
for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
|
|
262
|
+
for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
263
|
+
for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
264
|
+
for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
265
|
+
for (Rolls.psywave(f.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
|
|
266
|
+
for (Rolls.moveSlot(f.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
267
|
+
for (Rolls.moveSlot(f.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
268
|
+
for (Rolls.multiHit(f.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
269
|
+
for (Rolls.multiHit(f.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
270
|
+
for (Rolls.secondaryChance(f.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
271
|
+
for (Rolls.secondaryChance(f.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
272
|
+
for (Rolls.criticalHit(f.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
273
|
+
for (Rolls.criticalHit(f.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
|
|
274
|
+
|
|
275
|
+
var p1_dmg = Rolls.damage(f.p1, p1_hit);
|
|
267
276
|
while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
|
|
268
277
|
a.p1.damage = @intCast(p1_dmg.min);
|
|
269
278
|
|
|
270
|
-
var p2_dmg = Rolls.damage(
|
|
279
|
+
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
271
280
|
|
|
272
281
|
const p1_min: u9 = p1_dmg.min;
|
|
273
282
|
const p2_min: u9 = p2_dmg.min;
|
|
@@ -275,17 +284,14 @@ pub fn transitions(
|
|
|
275
284
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
276
285
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
277
286
|
|
|
278
|
-
opts.calc.overrides
|
|
287
|
+
opts.calc.overrides = a;
|
|
279
288
|
opts.calc.summaries = .{};
|
|
280
|
-
opts.chance = .{ .probability = .{} };
|
|
289
|
+
opts.chance = .{ .probability = .{}, .durations = d };
|
|
281
290
|
const q = &opts.chance.probability;
|
|
282
291
|
|
|
283
292
|
b = battle;
|
|
284
293
|
_ = try b.update(c1, c2, &opts);
|
|
285
294
|
|
|
286
|
-
d = options.durations;
|
|
287
|
-
try d.update(&b, &q, opts.chance.actions);
|
|
288
|
-
|
|
289
295
|
stats.updates += 1;
|
|
290
296
|
|
|
291
297
|
const summaries = &opts.calc.summaries;
|
|
@@ -296,8 +302,8 @@ pub fn transitions(
|
|
|
296
302
|
const p2_max: u9 =
|
|
297
303
|
try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
298
304
|
|
|
299
|
-
if (opts.chance.actions.matches(
|
|
300
|
-
if (!opts.chance.actions.eql(a)) {
|
|
305
|
+
if (opts.chance.actions.matches(f)) {
|
|
306
|
+
if (!opts.chance.actions.relax().eql(a)) {
|
|
301
307
|
if (!summary) {
|
|
302
308
|
try debug(writer, opts.chance.actions, .{
|
|
303
309
|
.p1_max = p1_max,
|
|
@@ -331,10 +337,13 @@ pub fn transitions(
|
|
|
331
337
|
}
|
|
332
338
|
}
|
|
333
339
|
}
|
|
334
|
-
|
|
335
340
|
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
336
341
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
342
|
+
|
|
343
|
+
q.reduce();
|
|
337
344
|
try p.add(q);
|
|
345
|
+
p.reduce();
|
|
346
|
+
|
|
338
347
|
stats.saved += 1;
|
|
339
348
|
|
|
340
349
|
if (p.q < p.p) {
|
|
@@ -342,7 +351,7 @@ pub fn transitions(
|
|
|
342
351
|
return error.TestUnexpectedResult;
|
|
343
352
|
}
|
|
344
353
|
} else {
|
|
345
|
-
if (!
|
|
354
|
+
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
346
355
|
try frontier.append(opts.chance.actions);
|
|
347
356
|
|
|
348
357
|
try debug(writer, opts.chance.actions, .{
|
|
@@ -372,9 +381,11 @@ pub fn transitions(
|
|
|
372
381
|
p2_dmg.min = p2_max;
|
|
373
382
|
}
|
|
374
383
|
|
|
375
|
-
}}}}}}}}}}}}}}}}}}}}
|
|
384
|
+
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
376
385
|
|
|
377
|
-
|
|
386
|
+
assert(stats.saved > saved);
|
|
387
|
+
const old = @hasDecl(std.fmt, "formatFloatDecimal");
|
|
388
|
+
if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
|
|
378
389
|
p.reduce();
|
|
379
390
|
try writer.print(
|
|
380
391
|
" = {s} ({d:.2}%)\n\n",
|
|
@@ -404,7 +415,6 @@ pub fn transitions(
|
|
|
404
415
|
|
|
405
416
|
pub fn update(
|
|
406
417
|
battle: anytype,
|
|
407
|
-
durations: *Durations,
|
|
408
418
|
c1: Choice,
|
|
409
419
|
c2: Choice,
|
|
410
420
|
options: anytype,
|
|
@@ -412,40 +422,38 @@ pub fn update(
|
|
|
412
422
|
writer: anytype,
|
|
413
423
|
transition: bool,
|
|
414
424
|
) !Result {
|
|
425
|
+
const durations = options.chance.durations;
|
|
415
426
|
options.chance.reset();
|
|
416
427
|
|
|
417
|
-
if (!pkmn.options.chance or !pkmn.options.calc
|
|
418
|
-
return battle.update(c1, c2, options);
|
|
419
|
-
}
|
|
428
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return battle.update(c1, c2, options);
|
|
420
429
|
|
|
421
|
-
|
|
422
|
-
const saved = durations.*;
|
|
423
|
-
const actions = options.chance.actions;
|
|
430
|
+
const original = battle.*;
|
|
424
431
|
|
|
425
432
|
// Perfom the actual update
|
|
426
433
|
const result = battle.update(c1, c2, options);
|
|
427
|
-
|
|
434
|
+
var overrides = options.chance.actions;
|
|
435
|
+
const updated = options.chance.durations;
|
|
428
436
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
437
|
+
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
438
|
+
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
439
|
+
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
440
|
+
var stream: protocol.ByteStream = .{ .buffer = &buf };
|
|
441
|
+
const n = try helpers.diff(&original, battle, stream.writer());
|
|
433
442
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (!pkmn.options.chance or !pkmn.options.calc) return result;
|
|
443
|
+
// Applying the diff to the battle should take us back to the original copy
|
|
444
|
+
// of the battle (ignoring the RNG).
|
|
445
|
+
var patched = battle.*;
|
|
446
|
+
patched.rng = original.rng;
|
|
447
|
+
helpers.patch(&patched, buf[0..n]);
|
|
448
|
+
try expectEqual(original, patched);
|
|
449
|
+
}
|
|
442
450
|
|
|
443
451
|
if (transition) {
|
|
444
452
|
// Ensure we can generate all transitions from the same original state
|
|
445
453
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
446
454
|
// the transitions function relies on RNG for discovery of states)
|
|
447
|
-
if (try transitions(unfix(
|
|
448
|
-
.durations =
|
|
455
|
+
if (try transitions(unfix(original), c1, c2, allocator, writer, .{
|
|
456
|
+
.durations = durations,
|
|
449
457
|
.cap = true,
|
|
450
458
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
451
459
|
}
|
|
@@ -454,21 +462,50 @@ pub fn update(
|
|
|
454
462
|
// same as we observed.
|
|
455
463
|
var override = pkmn.battle.options(
|
|
456
464
|
protocol.NULL,
|
|
457
|
-
Chance(Rational(u128)){ .probability = .{} },
|
|
458
|
-
Calc{ .overrides =
|
|
465
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
466
|
+
Calc{ .overrides = overrides },
|
|
459
467
|
);
|
|
460
|
-
|
|
468
|
+
var copy = original;
|
|
469
|
+
var overridden = copy.update(c1, c2, &override);
|
|
461
470
|
try expectEqual(result, overridden);
|
|
462
|
-
try
|
|
471
|
+
try expectEqualActions(overrides, override.chance.actions);
|
|
472
|
+
try expectEqual(updated, override.chance.durations);
|
|
463
473
|
|
|
464
474
|
// The actual battle excluding its RNG field should match a copy updated with
|
|
465
475
|
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
466
476
|
copy.rng = battle.rng;
|
|
467
|
-
try expectEqual(copy
|
|
477
|
+
try expectEqual(battle.*, copy);
|
|
478
|
+
|
|
479
|
+
// Demonstrate that even with the duration rolls changed, the duration
|
|
480
|
+
// fields on their own are sufficient to achieve the same outcomes
|
|
481
|
+
// NB: The actual battle bytes will differ because the durations won't match
|
|
482
|
+
overrides.p1.duration = if (overrides.p1.duration > 0) 2 else 0;
|
|
483
|
+
overrides.p2.duration = if (overrides.p2.duration > 0) 2 else 0;
|
|
484
|
+
|
|
485
|
+
override = pkmn.battle.options(
|
|
486
|
+
protocol.NULL,
|
|
487
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
488
|
+
Calc{ .overrides = overrides },
|
|
489
|
+
);
|
|
490
|
+
copy = original;
|
|
491
|
+
overridden = copy.update(c1, c2, &override);
|
|
492
|
+
try expectEqual(result, overridden);
|
|
493
|
+
try expectEqualActions(overrides, override.chance.actions);
|
|
494
|
+
try expectEqual(updated, override.chance.durations);
|
|
468
495
|
|
|
469
496
|
return result;
|
|
470
497
|
}
|
|
471
498
|
|
|
499
|
+
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
500
|
+
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
501
|
+
error.TestExpectedEqual => {
|
|
502
|
+
std.debug.print("expected {}, found {}\n", .{ expected, actual });
|
|
503
|
+
return e;
|
|
504
|
+
},
|
|
505
|
+
else => return e,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
472
509
|
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
473
510
|
return .{
|
|
474
511
|
.sides = actual.sides,
|
|
@@ -484,15 +521,6 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
484
521
|
};
|
|
485
522
|
}
|
|
486
523
|
|
|
487
|
-
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
488
|
-
for (frontier, 0..) |f, j| {
|
|
489
|
-
// TODO: is skipping this redundant check worth it?
|
|
490
|
-
if (i == j) continue;
|
|
491
|
-
if (f.matches(actions)) return true;
|
|
492
|
-
}
|
|
493
|
-
return false;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
524
|
fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
497
525
|
const w = std.io.getStdErr().writer();
|
|
498
526
|
w.print(fmt, v) catch return;
|
|
@@ -565,8 +593,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
|
|
|
565
593
|
try writer.writeAll(output[0..len]);
|
|
566
594
|
}
|
|
567
595
|
|
|
568
|
-
const NONE = @intFromEnum(Optional(bool).None);
|
|
569
|
-
|
|
570
596
|
/// Helper functions that efficiently return valid ranges for various RNG events based on the
|
|
571
597
|
/// state of an `Action` and other events to be used to construct a "transitions" function.
|
|
572
598
|
pub const Rolls = struct {
|
|
@@ -578,6 +604,108 @@ pub const Rolls = struct {
|
|
|
578
604
|
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
579
605
|
}
|
|
580
606
|
|
|
607
|
+
const OBS_NONE = [_]Optional(Observation){.None};
|
|
608
|
+
const OBS_STARTED = [_]Optional(Observation){.started};
|
|
609
|
+
const OBS_CONTINUING = [_]Optional(Observation){.continuing};
|
|
610
|
+
const OBS_ENDED = [_]Optional(Observation){.ended};
|
|
611
|
+
const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
|
|
612
|
+
const OBS = [_]Optional(Observation){ .continuing, .ended };
|
|
613
|
+
|
|
614
|
+
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
615
|
+
/// `durations`.
|
|
616
|
+
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
|
|
617
|
+
if (action.sleep == .None) return &OBS_NONE;
|
|
618
|
+
return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
|
|
619
|
+
&OBS_ALL
|
|
620
|
+
else switch (action.sleep) {
|
|
621
|
+
.started => &OBS_STARTED,
|
|
622
|
+
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
627
|
+
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
628
|
+
pub fn disable(
|
|
629
|
+
action: Action,
|
|
630
|
+
duration: Duration,
|
|
631
|
+
parent: Optional(Observation),
|
|
632
|
+
) []const Optional(Observation) {
|
|
633
|
+
if (action.disable == .None) return &OBS_NONE;
|
|
634
|
+
return if (duration.disable == 0 and action.speed_tie != .None)
|
|
635
|
+
&OBS_ALL
|
|
636
|
+
else switch (action.disable) {
|
|
637
|
+
.started => &OBS_STARTED,
|
|
638
|
+
else => if (duration.disable >= 8)
|
|
639
|
+
&OBS_ENDED
|
|
640
|
+
else if (parent != .None and parent != .started)
|
|
641
|
+
&OBS_CONTINUING
|
|
642
|
+
else
|
|
643
|
+
&OBS,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/// FIXME
|
|
648
|
+
pub fn attacking(
|
|
649
|
+
action: Action,
|
|
650
|
+
duration: Duration,
|
|
651
|
+
parent: Optional(Observation),
|
|
652
|
+
) []const Optional(Observation) {
|
|
653
|
+
return switch (action.attacking) {
|
|
654
|
+
.None => &OBS_NONE,
|
|
655
|
+
.started => &OBS_STARTED,
|
|
656
|
+
else => if (duration.attacking >= 3)
|
|
657
|
+
&OBS_ENDED
|
|
658
|
+
else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
|
|
659
|
+
&OBS_CONTINUING
|
|
660
|
+
else
|
|
661
|
+
&OBS,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const CFZ_NONE = [_]Optional(Confusion){.None};
|
|
666
|
+
const CFZ_STARTED = [_]Optional(Confusion){.started};
|
|
667
|
+
const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
|
|
668
|
+
const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
|
|
669
|
+
const CFZ_ENDED = [_]Optional(Confusion){.ended};
|
|
670
|
+
const CFZ = [_]Optional(Confusion){ .continuing, .ended };
|
|
671
|
+
|
|
672
|
+
/// FIXME
|
|
673
|
+
pub fn confusion(
|
|
674
|
+
action: Action,
|
|
675
|
+
duration: Duration,
|
|
676
|
+
sibling: Optional(Observation),
|
|
677
|
+
parent: Optional(Observation),
|
|
678
|
+
) []const Optional(Confusion) {
|
|
679
|
+
return switch (action.confusion) {
|
|
680
|
+
.None => &CFZ_NONE,
|
|
681
|
+
.started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
|
|
682
|
+
&CFZ_OVERWRITTEN
|
|
683
|
+
else
|
|
684
|
+
&CFZ_STARTED,
|
|
685
|
+
else => if (duration.confusion >= 5)
|
|
686
|
+
&CFZ_ENDED
|
|
687
|
+
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
688
|
+
&CFZ_CONTINUING
|
|
689
|
+
else
|
|
690
|
+
&CFZ,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
695
|
+
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
696
|
+
pub fn binding(
|
|
697
|
+
action: Action,
|
|
698
|
+
duration: Duration,
|
|
699
|
+
parent: Optional(bool),
|
|
700
|
+
) []const Optional(Observation) {
|
|
701
|
+
if (parent == .true) return &OBS_NONE;
|
|
702
|
+
return switch (action.binding) {
|
|
703
|
+
.None => &OBS_NONE,
|
|
704
|
+
.started => &OBS_STARTED,
|
|
705
|
+
else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
581
709
|
const BOOL_NONE = [_]Optional(bool){.None};
|
|
582
710
|
const BOOLS = [_]Optional(bool){ .false, .true };
|
|
583
711
|
|
|
@@ -591,7 +719,7 @@ pub const Rolls = struct {
|
|
|
591
719
|
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
592
720
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
593
721
|
pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
594
|
-
if (parent == .false) return &BOOL_NONE;
|
|
722
|
+
if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
|
|
595
723
|
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
596
724
|
}
|
|
597
725
|
|
|
@@ -626,9 +754,11 @@ pub const Rolls = struct {
|
|
|
626
754
|
return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
|
|
627
755
|
}
|
|
628
756
|
|
|
629
|
-
/// Returns a slice with the correct range of values for confused
|
|
630
|
-
|
|
631
|
-
|
|
757
|
+
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
758
|
+
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
759
|
+
pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
|
|
760
|
+
const done = parent == .None or parent == .ended;
|
|
761
|
+
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
632
762
|
}
|
|
633
763
|
|
|
634
764
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
@@ -638,17 +768,6 @@ pub const Rolls = struct {
|
|
|
638
768
|
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
639
769
|
}
|
|
640
770
|
|
|
641
|
-
const DURATION_NONE = [_]u2{0};
|
|
642
|
-
// NB: DURATION must be at least 2 because that is the maximum of the minimums for all rolls
|
|
643
|
-
const DURATION = [_]u2{2};
|
|
644
|
-
|
|
645
|
-
/// Returns a slice with a range of values for duration given the `action` state
|
|
646
|
-
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
647
|
-
pub fn duration(action: Action, parent: Optional(bool)) []const u2 {
|
|
648
|
-
if (parent == .false) return &DURATION_NONE;
|
|
649
|
-
return if (action.duration == 0) &DURATION_NONE else &DURATION;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
771
|
const SLOT_NONE = [_]u4{0};
|
|
653
772
|
const SLOT = [_]u4{ 1, 2, 3, 4 };
|
|
654
773
|
|
|
@@ -706,6 +825,26 @@ test "Rolls.speedTie" {
|
|
|
706
825
|
try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
|
|
707
826
|
}
|
|
708
827
|
|
|
828
|
+
test "Rolls.sleep" {
|
|
829
|
+
return error.SkipZigTest; // TODO
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
test "Rolls.disable" {
|
|
833
|
+
return error.SkipZigTest; // TODO
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
test "Rolls.confusion" {
|
|
837
|
+
return error.SkipZigTest; // TODO
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
test "Rolls.atacking" {
|
|
841
|
+
return error.SkipZigTest; // TODO
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
test "Rolls.binding" {
|
|
845
|
+
return error.SkipZigTest; // TODO
|
|
846
|
+
}
|
|
847
|
+
|
|
709
848
|
test "Rolls.damage" {
|
|
710
849
|
const actions: Actions = .{ .p2 = .{ .damage = 221 } };
|
|
711
850
|
try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
|
|
@@ -715,12 +854,12 @@ test "Rolls.damage" {
|
|
|
715
854
|
|
|
716
855
|
test "Rolls.coalesce" {
|
|
717
856
|
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
718
|
-
try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
|
|
719
|
-
try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
|
|
720
|
-
try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
|
|
857
|
+
try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
|
|
858
|
+
try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
|
|
859
|
+
try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
|
|
721
860
|
summaries.p1.damage.final = 74;
|
|
722
|
-
try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
|
|
723
|
-
try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
|
|
861
|
+
try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
|
|
862
|
+
try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
|
|
724
863
|
}
|
|
725
864
|
|
|
726
865
|
test "Rolls.hit" {
|
|
@@ -749,14 +888,23 @@ test "Rolls.criticalHit" {
|
|
|
749
888
|
&.{ .false, .true },
|
|
750
889
|
Rolls.criticalHit(actions.p1, .None),
|
|
751
890
|
);
|
|
752
|
-
try expectEqualSlices(
|
|
891
|
+
try expectEqualSlices(
|
|
892
|
+
Optional(bool),
|
|
893
|
+
if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
|
|
894
|
+
Rolls.criticalHit(actions.p1, .false),
|
|
895
|
+
);
|
|
753
896
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
|
|
754
897
|
}
|
|
755
898
|
|
|
756
899
|
test "Rolls.confused" {
|
|
757
900
|
const actions: Actions = .{ .p2 = .{ .confused = .true } };
|
|
758
|
-
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
|
|
759
|
-
try expectEqualSlices(
|
|
901
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
|
|
902
|
+
try expectEqualSlices(
|
|
903
|
+
Optional(bool),
|
|
904
|
+
&.{ .false, .true },
|
|
905
|
+
Rolls.confused(actions.p2, .continuing),
|
|
906
|
+
);
|
|
907
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
|
|
760
908
|
}
|
|
761
909
|
|
|
762
910
|
test "Rolls.paralyzed" {
|
|
@@ -767,13 +915,6 @@ test "Rolls.paralyzed" {
|
|
|
767
915
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p2, .true));
|
|
768
916
|
}
|
|
769
917
|
|
|
770
|
-
test "Rolls.duration" {
|
|
771
|
-
const actions: Actions = .{ .p2 = .{ .duration = 3 } };
|
|
772
|
-
try expectEqualSlices(u2, &.{0}, Rolls.duration(actions.p1, .None));
|
|
773
|
-
try expectEqualSlices(u2, &.{2}, Rolls.duration(actions.p2, .None));
|
|
774
|
-
try expectEqualSlices(u2, &.{0}, Rolls.duration(actions.p2, .false));
|
|
775
|
-
}
|
|
776
|
-
|
|
777
918
|
test "Rolls.moveSlot" {
|
|
778
919
|
const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
|
|
779
920
|
try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
|
|
@@ -804,9 +945,9 @@ test "Rolls.psywave" {
|
|
|
804
945
|
|
|
805
946
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
806
947
|
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
807
|
-
try expectEqual(150, rolls[rolls.len - 1]);
|
|
948
|
+
try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
|
|
808
949
|
side.stored().level = 81;
|
|
809
950
|
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
810
|
-
try expectEqual(121, rolls[rolls.len - 1]);
|
|
951
|
+
try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
|
|
811
952
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
812
953
|
}
|