@pkmn/engine 0.1.0-dev.ce4c0b2a → 0.1.0-dev.d127cb34
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 -1
- package/README.md +23 -15
- package/build/pkg/addon/browser.d.ts +1 -1
- package/build/pkg/addon/browser.js +6 -2
- package/build/pkg/addon/browser.js.map +1 -1
- package/build/pkg/addon/node.js +22 -8
- package/build/pkg/addon/node.js.map +1 -1
- package/build/pkg/addon/wasm.d.ts +2 -0
- package/build/pkg/addon/wasm.js +53 -0
- package/build/pkg/addon/wasm.js.map +1 -0
- package/build/pkg/addon.d.ts +25 -5
- package/build/pkg/addon.js +8 -17
- 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.js.map +1 -1
- package/build/pkg/gen1.d.ts +15 -18
- package/build/pkg/gen1.js +28 -19
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +37 -100
- package/build/pkg/index.js +23 -75
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +11 -2
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +56 -13
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +19 -81
- package/build/tools/display/debug.jsx.map +1 -1
- package/build/tools/display/index.js +35 -15
- package/build/tools/display/index.js.map +1 -1
- package/build/tools/display/ui.jsx +27 -16
- package/build/tools/display/ui.jsx.map +1 -1
- package/build/tools/display/util.js +141 -2
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +330 -245
- package/package.json +36 -128
- package/src/bin/install-pkmn-engine +25 -9
- package/src/lib/bindings/c.zig +201 -0
- package/src/lib/bindings/node.zig +117 -0
- package/src/lib/bindings/wasm.zig +51 -0
- package/src/lib/common/array.zig +5 -12
- package/src/lib/common/debug.zig +45 -34
- package/src/lib/common/js.zig +3 -9
- package/src/lib/common/optional.zig +26 -26
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +228 -197
- package/src/lib/common/rational.zig +278 -285
- package/src/lib/common/rng.zig +1 -3
- package/src/lib/common/util.zig +34 -31
- package/src/lib/gen1/calc.zig +220 -195
- package/src/lib/gen1/chance.zig +184 -108
- 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 +3 -4
- package/src/lib/gen1/data.zig +12 -16
- package/src/lib/gen1/helpers.zig +35 -40
- package/src/lib/gen1/mechanics.zig +147 -127
- package/src/lib/gen2/calc.zig +12 -23
- package/src/lib/gen2/chance.zig +38 -26
- 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 +7 -5
- package/src/lib/gen2/data.zig +19 -22
- package/src/lib/gen2/generated.zig +18 -23
- package/src/lib/gen2/helpers.zig +32 -31
- package/src/lib/gen2/mechanics.zig +58 -55
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +4 -121
- package/src/lib/pkmn.zig +98 -19
- package/src/lib/wasm.zig +9 -9
- package/src/tools/display/dom.ts +43 -31
- package/src/tools/display/ui.css +12 -9
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -1,52 +1,39 @@
|
|
|
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 Duration = chance.Duration;
|
|
20
|
+
const Durations = chance.Durations;
|
|
21
|
+
const enabled = pkmn.options.calc;
|
|
18
22
|
const expect = std.testing.expect;
|
|
19
23
|
const expectEqual = std.testing.expectEqual;
|
|
20
24
|
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
21
|
-
|
|
22
|
-
const enabled = pkmn.options.calc;
|
|
23
|
-
|
|
24
|
-
const Player = common.Player;
|
|
25
|
-
const Result = common.Result;
|
|
26
|
-
const Choice = common.Choice;
|
|
27
|
-
|
|
28
|
-
const Optional = optional.Optional;
|
|
29
|
-
|
|
30
|
-
const Rational = rational.Rational;
|
|
31
|
-
|
|
32
|
-
const PointerType = util.PointerType;
|
|
33
25
|
const isPointerTo = util.isPointerTo;
|
|
34
|
-
|
|
35
|
-
const Actions = chance.Actions;
|
|
36
|
-
const Action = chance.Action;
|
|
37
|
-
const Chance = chance.Chance;
|
|
38
|
-
const Confusion = chance.Confusion;
|
|
39
|
-
const Duration = chance.Duration;
|
|
40
|
-
const Durations = chance.Durations;
|
|
41
26
|
const Observation = chance.Observation;
|
|
27
|
+
const Optional = optional.Optional;
|
|
28
|
+
const Player = common.Player;
|
|
29
|
+
const PointerType = util.PointerType;
|
|
30
|
+
const Rational = rational.Rational;
|
|
31
|
+
const Result = common.Result;
|
|
42
32
|
const Sleeps = chance.Sleeps;
|
|
43
33
|
|
|
44
34
|
const tty = true; // DEBUG
|
|
45
35
|
const summary = false; // DEBUG
|
|
46
36
|
|
|
47
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
48
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
49
|
-
|
|
50
37
|
/// Information relevant to damage calculation that occured during a Generation I battle `update`.
|
|
51
38
|
pub const Summaries = extern struct {
|
|
52
39
|
/// Relevant information for Player 1.
|
|
@@ -108,17 +95,25 @@ pub const Calc = struct {
|
|
|
108
95
|
self: Calc,
|
|
109
96
|
player: Player,
|
|
110
97
|
comptime field: Action.Field,
|
|
111
|
-
)
|
|
98
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
112
99
|
if (!enabled) return null;
|
|
113
100
|
|
|
114
101
|
const val = @field(self.overrides.get(player), @tagName(field));
|
|
115
102
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
116
|
-
|
|
117
|
-
|
|
103
|
+
.@"enum" => val != .None,
|
|
104
|
+
.int => val != 0,
|
|
118
105
|
else => unreachable,
|
|
119
106
|
}) val else null;
|
|
120
107
|
}
|
|
121
108
|
|
|
109
|
+
pub fn confusion(self: *Calc, player: Player) void {
|
|
110
|
+
if (!enabled) return;
|
|
111
|
+
|
|
112
|
+
if (self.overrides.get(player).confusion == .started) {
|
|
113
|
+
self.overrides.get(player).confusion = .None;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
122
117
|
pub fn base(self: *Calc, player: Player, val: u16) void {
|
|
123
118
|
if (!enabled) return;
|
|
124
119
|
|
|
@@ -147,11 +142,15 @@ const Null = struct {
|
|
|
147
142
|
self: Null,
|
|
148
143
|
player: Player,
|
|
149
144
|
comptime field: Action.Field,
|
|
150
|
-
)
|
|
145
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
151
146
|
_ = .{ self, player };
|
|
152
147
|
return null;
|
|
153
148
|
}
|
|
154
149
|
|
|
150
|
+
pub fn confusion(self: Null, player: Player) void {
|
|
151
|
+
_ = .{ self, player };
|
|
152
|
+
}
|
|
153
|
+
|
|
155
154
|
pub fn base(self: Null, player: Player, val: u16) void {
|
|
156
155
|
_ = .{ self, player, val };
|
|
157
156
|
}
|
|
@@ -179,30 +178,31 @@ pub const Options = struct {
|
|
|
179
178
|
metronome: bool = false,
|
|
180
179
|
};
|
|
181
180
|
|
|
182
|
-
pub const MAX_FRONTIER =
|
|
181
|
+
pub const MAX_FRONTIER = 86;
|
|
183
182
|
|
|
184
183
|
pub fn transitions(
|
|
185
184
|
battle: anytype,
|
|
186
185
|
c1: Choice,
|
|
187
186
|
c2: Choice,
|
|
188
187
|
allocator: std.mem.Allocator,
|
|
189
|
-
|
|
188
|
+
out: *std.Io.Writer,
|
|
189
|
+
err: *std.Io.Writer,
|
|
190
190
|
options: Options,
|
|
191
191
|
) !?Stats {
|
|
192
192
|
var stats: Stats = .{};
|
|
193
193
|
|
|
194
194
|
const cap = options.cap;
|
|
195
195
|
|
|
196
|
-
var seen
|
|
196
|
+
var seen: std.AutoHashMap(Actions, void) = .init(allocator);
|
|
197
197
|
defer seen.deinit();
|
|
198
|
-
var frontier
|
|
199
|
-
defer frontier.deinit();
|
|
198
|
+
var frontier: std.array_list.Aligned(Actions, null) = .empty;
|
|
199
|
+
defer frontier.deinit(allocator);
|
|
200
200
|
|
|
201
|
-
const
|
|
201
|
+
const d = options.durations;
|
|
202
202
|
|
|
203
203
|
var opts = pkmn.battle.options(
|
|
204
204
|
protocol.NULL,
|
|
205
|
-
Chance(Rational(u128)){ .probability = .{}, .durations =
|
|
205
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = d },
|
|
206
206
|
Calc{},
|
|
207
207
|
);
|
|
208
208
|
|
|
@@ -213,8 +213,8 @@ pub fn transitions(
|
|
|
213
213
|
const p1 = b.side(.P1);
|
|
214
214
|
const p2 = b.side(.P2);
|
|
215
215
|
|
|
216
|
-
var p: Rational(
|
|
217
|
-
try frontier.append(opts.chance.actions);
|
|
216
|
+
var p: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
217
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
218
218
|
|
|
219
219
|
// zig fmt: off
|
|
220
220
|
for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
|
|
@@ -228,7 +228,9 @@ pub fn transitions(
|
|
|
228
228
|
const f = frontier.items[i];
|
|
229
229
|
const saved = stats.saved;
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
var r: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
232
|
+
|
|
233
|
+
try debug(out, f, .{
|
|
232
234
|
.shape = true,
|
|
233
235
|
.color = i,
|
|
234
236
|
.bold = true,
|
|
@@ -242,20 +244,20 @@ pub fn transitions(
|
|
|
242
244
|
};
|
|
243
245
|
|
|
244
246
|
for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
245
|
-
for (Rolls.sleep(f.p1,
|
|
246
|
-
for (Rolls.sleep(f.p2,
|
|
247
|
-
for (Rolls.disable(f.p1,
|
|
248
|
-
for (Rolls.disable(f.p2,
|
|
249
|
-
for (Rolls.
|
|
250
|
-
for (Rolls.
|
|
247
|
+
for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
|
|
248
|
+
for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
|
|
249
|
+
for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
|
|
250
|
+
for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
|
|
251
|
+
for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
|
|
252
|
+
for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
|
|
253
|
+
for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
254
|
+
for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
251
255
|
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
252
256
|
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
253
257
|
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
254
258
|
for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
255
|
-
for (Rolls.
|
|
256
|
-
for (Rolls.
|
|
257
|
-
for (Rolls.binding(f.p1, durations.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
|
|
258
|
-
for (Rolls.binding(f.p2, durations.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
|
|
259
|
+
for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
|
|
260
|
+
for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
|
|
259
261
|
for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
260
262
|
for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
261
263
|
for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
@@ -275,15 +277,15 @@ pub fn transitions(
|
|
|
275
277
|
|
|
276
278
|
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
277
279
|
|
|
278
|
-
const p1_min: u9 = p1_dmg.min;
|
|
279
|
-
const p2_min: u9 = p2_dmg.min;
|
|
280
|
+
const p1_min: u9 = @intCast(p1_dmg.min);
|
|
281
|
+
const p2_min: u9 = @intCast(p2_dmg.min);
|
|
280
282
|
|
|
281
283
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
282
284
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
283
285
|
|
|
284
286
|
opts.calc.overrides = a;
|
|
285
287
|
opts.calc.summaries = .{};
|
|
286
|
-
opts.chance = .{ .probability = .{}, .durations =
|
|
288
|
+
opts.chance = .{ .probability = .{}, .durations = d };
|
|
287
289
|
const q = &opts.chance.probability;
|
|
288
290
|
|
|
289
291
|
b = battle;
|
|
@@ -293,16 +295,16 @@ pub fn transitions(
|
|
|
293
295
|
|
|
294
296
|
const summaries = &opts.calc.summaries;
|
|
295
297
|
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
296
|
-
p1_dmg.min
|
|
298
|
+
@intCast(p1_dmg.min)
|
|
297
299
|
else
|
|
298
|
-
|
|
300
|
+
Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
299
301
|
const p2_max: u9 =
|
|
300
|
-
|
|
302
|
+
Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
301
303
|
|
|
302
304
|
if (opts.chance.actions.matches(f)) {
|
|
303
|
-
if (!
|
|
305
|
+
if (!opts.chance.actions.relax().eql(a)) {
|
|
304
306
|
if (!summary) {
|
|
305
|
-
try debug(
|
|
307
|
+
try debug(out, opts.chance.actions, .{
|
|
306
308
|
.p1_max = p1_max,
|
|
307
309
|
.p2_max = p2_max,
|
|
308
310
|
.color = i,
|
|
@@ -316,7 +318,7 @@ pub fn transitions(
|
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
if (!summary) {
|
|
319
|
-
try debug(
|
|
321
|
+
try debug(out, opts.chance.actions, .{
|
|
320
322
|
.p1_max = p1_max,
|
|
321
323
|
.p2_max = p2_max,
|
|
322
324
|
.color = i,
|
|
@@ -329,33 +331,38 @@ pub fn transitions(
|
|
|
329
331
|
acts.p1.damage = @intCast(p1d);
|
|
330
332
|
acts.p2.damage = @intCast(p2d);
|
|
331
333
|
if ((try seen.getOrPut(acts)).found_existing) {
|
|
332
|
-
err
|
|
334
|
+
die(err, "already seen {f}", .{acts}, options.seed);
|
|
333
335
|
return error.TestUnexpectedResult;
|
|
334
336
|
}
|
|
335
337
|
}
|
|
336
338
|
}
|
|
337
|
-
|
|
338
339
|
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
339
340
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
341
|
+
|
|
342
|
+
q.reduce();
|
|
340
343
|
try p.add(q);
|
|
344
|
+
p.reduce();
|
|
345
|
+
try r.add(q);
|
|
346
|
+
r.reduce();
|
|
347
|
+
|
|
341
348
|
stats.saved += 1;
|
|
342
349
|
|
|
343
350
|
if (p.q < p.p) {
|
|
344
|
-
err
|
|
351
|
+
die(err, "improper fraction {f}", .{p}, options.seed);
|
|
345
352
|
return error.TestUnexpectedResult;
|
|
346
353
|
}
|
|
347
354
|
} else {
|
|
348
|
-
if (!
|
|
349
|
-
try frontier.append(opts.chance.actions);
|
|
355
|
+
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
356
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
350
357
|
|
|
351
|
-
try debug(
|
|
358
|
+
try debug(out, opts.chance.actions, .{
|
|
352
359
|
.p1_max = p1_max,
|
|
353
360
|
.p2_max = p2_max,
|
|
354
361
|
.dim = true,
|
|
355
362
|
.newline = false,
|
|
356
363
|
});
|
|
357
|
-
try
|
|
358
|
-
try debug(
|
|
364
|
+
try out.writeAll(" → ");
|
|
365
|
+
try debug(out, opts.chance.actions, .{
|
|
359
366
|
.shape = true,
|
|
360
367
|
.color = frontier.items.len - 1,
|
|
361
368
|
.dim = true,
|
|
@@ -363,7 +370,7 @@ pub fn transitions(
|
|
|
363
370
|
.indent = false,
|
|
364
371
|
});
|
|
365
372
|
} else if (!summary) {
|
|
366
|
-
try debug(
|
|
373
|
+
try debug(out, opts.chance.actions, .{
|
|
367
374
|
.p1_max = p1_max,
|
|
368
375
|
.p2_max = p2_max,
|
|
369
376
|
.dim = true,
|
|
@@ -378,14 +385,12 @@ pub fn transitions(
|
|
|
378
385
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
379
386
|
|
|
380
387
|
assert(stats.saved > saved);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
);
|
|
388
|
-
}
|
|
388
|
+
// TODO: LLVM - float_from_int from 'u256' without intrinsics
|
|
389
|
+
// try out.print(
|
|
390
|
+
// " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
|
|
391
|
+
// .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
|
|
392
|
+
// p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
393
|
+
// );
|
|
389
394
|
|
|
390
395
|
}
|
|
391
396
|
|
|
@@ -400,7 +405,7 @@ pub fn transitions(
|
|
|
400
405
|
|
|
401
406
|
p.reduce();
|
|
402
407
|
if (p.p != 1 or p.q != 1) {
|
|
403
|
-
err
|
|
408
|
+
die(err, "expected 1, found {f}", .{p}, options.seed);
|
|
404
409
|
return error.TestExpectedEqual;
|
|
405
410
|
}
|
|
406
411
|
|
|
@@ -413,7 +418,8 @@ pub fn update(
|
|
|
413
418
|
c2: Choice,
|
|
414
419
|
options: anytype,
|
|
415
420
|
allocator: std.mem.Allocator,
|
|
416
|
-
|
|
421
|
+
out: *std.Io.Writer,
|
|
422
|
+
err: *std.Io.Writer,
|
|
417
423
|
transition: bool,
|
|
418
424
|
) !Result {
|
|
419
425
|
const durations = options.chance.durations;
|
|
@@ -431,8 +437,8 @@ pub fn update(
|
|
|
431
437
|
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
432
438
|
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
433
439
|
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
434
|
-
var
|
|
435
|
-
const n = try helpers.diff(&original, battle,
|
|
440
|
+
var pw: protocol.Writer = .{ .buffer = &buf };
|
|
441
|
+
const n = try helpers.diff(&original, battle, &pw);
|
|
436
442
|
|
|
437
443
|
// Applying the diff to the battle should take us back to the original copy
|
|
438
444
|
// of the battle (ignoring the RNG).
|
|
@@ -446,7 +452,7 @@ pub fn update(
|
|
|
446
452
|
// Ensure we can generate all transitions from the same original state
|
|
447
453
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
448
454
|
// the transitions function relies on RNG for discovery of states)
|
|
449
|
-
if (try transitions(unfix(original), c1, c2, allocator,
|
|
455
|
+
if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
|
|
450
456
|
.durations = durations,
|
|
451
457
|
.cap = true,
|
|
452
458
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
@@ -493,10 +499,9 @@ pub fn update(
|
|
|
493
499
|
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
494
500
|
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
495
501
|
error.TestExpectedEqual => {
|
|
496
|
-
std.debug.print("expected {}, found {}\n", .{ expected, actual });
|
|
502
|
+
std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
|
|
497
503
|
return e;
|
|
498
504
|
},
|
|
499
|
-
else => return e,
|
|
500
505
|
};
|
|
501
506
|
}
|
|
502
507
|
|
|
@@ -515,27 +520,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
515
520
|
};
|
|
516
521
|
}
|
|
517
522
|
|
|
518
|
-
fn
|
|
519
|
-
if (!pkmn.options.overwrite) return actions;
|
|
520
|
-
var a = actions;
|
|
521
|
-
if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
|
|
522
|
-
if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
|
|
523
|
-
return a;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
527
|
-
for (frontier, 0..) |f, j| {
|
|
528
|
-
// TODO: is skipping this redundant check worth it?
|
|
529
|
-
if (i == j) continue;
|
|
530
|
-
if (f.matches(actions)) return true;
|
|
531
|
-
}
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
536
|
-
const w = std.io.getStdErr().writer();
|
|
523
|
+
fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
537
524
|
w.print(fmt, v) catch return;
|
|
538
|
-
if (seed) |s| return w.print("{}\n", .{s}) catch return;
|
|
525
|
+
if (seed) |s| return w.print("{d}\n", .{s}) catch return;
|
|
539
526
|
return w.writeByte('\n') catch return;
|
|
540
527
|
}
|
|
541
528
|
|
|
@@ -551,7 +538,7 @@ const Style = struct {
|
|
|
551
538
|
indent: bool = true,
|
|
552
539
|
};
|
|
553
540
|
|
|
554
|
-
fn debug(writer:
|
|
541
|
+
fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
|
|
555
542
|
if (style.indent) try writer.writeAll(" ");
|
|
556
543
|
if (tty) {
|
|
557
544
|
const mod: usize = if (style.dim) 2 else 1;
|
|
@@ -576,13 +563,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
|
|
|
576
563
|
if (style.newline) try writer.writeByte('\n');
|
|
577
564
|
}
|
|
578
565
|
|
|
579
|
-
fn format(writer:
|
|
566
|
+
fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
|
|
580
567
|
var input: [1024]u8 = undefined;
|
|
581
568
|
var output: [1024]u8 = undefined;
|
|
582
569
|
|
|
583
|
-
var
|
|
584
|
-
try actions.fmt(&
|
|
585
|
-
var len =
|
|
570
|
+
var w = std.Io.Writer.fixed(&input);
|
|
571
|
+
try actions.fmt(&w, false);
|
|
572
|
+
var len = w.buffered().len;
|
|
586
573
|
@memcpy(output[0..len], input[0..len]);
|
|
587
574
|
|
|
588
575
|
var find: [16]u8 = undefined;
|
|
@@ -615,85 +602,113 @@ pub const Rolls = struct {
|
|
|
615
602
|
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
616
603
|
}
|
|
617
604
|
|
|
618
|
-
const
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
const
|
|
605
|
+
const SLP_NONE = [_]Optional(Observation(.sleep)){.None};
|
|
606
|
+
const SLP_STARTED = [_]Optional(Observation(.sleep)){.started};
|
|
607
|
+
const SLP_ENDED = [_]Optional(Observation(.sleep)){.ended};
|
|
608
|
+
const SLP_ALL = [_]Optional(Observation(.sleep)){ .started, .continuing, .ended };
|
|
609
|
+
const SLP = [_]Optional(Observation(.sleep)){ .continuing, .ended };
|
|
623
610
|
|
|
624
611
|
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
625
612
|
/// `durations`.
|
|
626
|
-
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
613
|
+
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation(.sleep)) {
|
|
614
|
+
if (action.sleep == .None) return &SLP_NONE;
|
|
615
|
+
return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
|
|
616
|
+
&SLP_ALL
|
|
617
|
+
else switch (action.sleep) {
|
|
618
|
+
.started => &SLP_STARTED,
|
|
619
|
+
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &SLP_ENDED else &SLP,
|
|
631
620
|
};
|
|
632
621
|
}
|
|
633
622
|
|
|
623
|
+
const DIS_NONE = [_]Optional(Observation(.disable)){.None};
|
|
624
|
+
const DIS_STARTED = [_]Optional(Observation(.disable)){.started};
|
|
625
|
+
const DIS_CONTINUING = [_]Optional(Observation(.disable)){.continuing};
|
|
626
|
+
const DIS_ENDED = [_]Optional(Observation(.disable)){.ended};
|
|
627
|
+
const DIS_ALL = [_]Optional(Observation(.disable)){ .started, .continuing, .ended };
|
|
628
|
+
const DIS = [_]Optional(Observation(.disable)){ .continuing, .ended };
|
|
629
|
+
|
|
634
630
|
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
635
631
|
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
636
632
|
pub fn disable(
|
|
637
633
|
action: Action,
|
|
638
634
|
duration: Duration,
|
|
639
|
-
parent: Optional(Observation),
|
|
640
|
-
) []const Optional(Observation) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
.started => &
|
|
646
|
-
else => if (duration.disable >= 8)
|
|
635
|
+
parent: Optional(Observation(.sleep)),
|
|
636
|
+
) []const Optional(Observation(.disable)) {
|
|
637
|
+
if (action.disable == .None) return &DIS_NONE;
|
|
638
|
+
return if (duration.disable == 0 and action.speed_tie != .None)
|
|
639
|
+
&DIS_ALL
|
|
640
|
+
else switch (action.disable) {
|
|
641
|
+
.started => &DIS_STARTED,
|
|
642
|
+
else => if (duration.disable >= 8)
|
|
643
|
+
&DIS_ENDED
|
|
644
|
+
else if (parent != .None and parent != .started)
|
|
645
|
+
&DIS_CONTINUING
|
|
646
|
+
else
|
|
647
|
+
&DIS,
|
|
647
648
|
};
|
|
648
649
|
}
|
|
649
650
|
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
const
|
|
651
|
+
const ATK_NONE = [_]Optional(Observation(.attacking)){.None};
|
|
652
|
+
const ATK_STARTED = [_]Optional(Observation(.attacking)){.started};
|
|
653
|
+
const ATK_CONTINUING = [_]Optional(Observation(.attacking)){.continuing};
|
|
654
|
+
const ATK_ENDED = [_]Optional(Observation(.attacking)){.ended};
|
|
655
|
+
const ATK = [_]Optional(Observation(.attacking)){ .continuing, .ended };
|
|
655
656
|
|
|
656
|
-
///
|
|
657
|
-
|
|
657
|
+
/// FIXME document
|
|
658
|
+
pub fn attacking(
|
|
659
|
+
action: Action,
|
|
660
|
+
duration: Duration,
|
|
661
|
+
parent: Optional(Observation(.sleep)),
|
|
662
|
+
) []const Optional(Observation(.attacking)) {
|
|
663
|
+
return switch (action.attacking) {
|
|
664
|
+
.None => &ATK_NONE,
|
|
665
|
+
.started => &ATK_STARTED,
|
|
666
|
+
else => if (duration.attacking >= 3)
|
|
667
|
+
&ATK_ENDED
|
|
668
|
+
else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
|
|
669
|
+
&ATK_CONTINUING
|
|
670
|
+
else
|
|
671
|
+
&ATK,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const CFZ_NONE = [_]Optional(Observation(.confusion)){.None};
|
|
676
|
+
const CFZ_STARTED = [_]Optional(Observation(.confusion)){.started};
|
|
677
|
+
const CFZ_OVERWRITTEN = [_]Optional(Observation(.confusion)){ .started, .overwritten };
|
|
678
|
+
const CFZ_CONTINUING = [_]Optional(Observation(.confusion)){.continuing};
|
|
679
|
+
const CFZ_ENDED = [_]Optional(Observation(.confusion)){.ended};
|
|
680
|
+
const CFZ = [_]Optional(Observation(.confusion)){ .continuing, .ended };
|
|
681
|
+
const CFZ_TIE = [_]Optional(Observation(.confusion)){ .started, .continuing, .ended };
|
|
682
|
+
|
|
683
|
+
/// FIXME document
|
|
658
684
|
pub fn confusion(
|
|
659
685
|
action: Action,
|
|
660
686
|
duration: Duration,
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
687
|
+
tie: Optional(Player),
|
|
688
|
+
sibling: Optional(Observation(.attacking)),
|
|
689
|
+
parent: Optional(Observation(.sleep)),
|
|
690
|
+
) []const Optional(Observation(.confusion)) {
|
|
665
691
|
return switch (action.confusion) {
|
|
666
692
|
.None => &CFZ_NONE,
|
|
667
|
-
.started =>
|
|
693
|
+
.started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
|
|
694
|
+
&CFZ_OVERWRITTEN
|
|
695
|
+
else
|
|
696
|
+
&CFZ_STARTED,
|
|
668
697
|
else => if (duration.confusion >= 5)
|
|
669
698
|
&CFZ_ENDED
|
|
670
|
-
else if (duration.confusion < 2)
|
|
699
|
+
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
671
700
|
&CFZ_CONTINUING
|
|
701
|
+
else if (tie != .None)
|
|
702
|
+
&CFZ_TIE
|
|
672
703
|
else
|
|
673
704
|
&CFZ,
|
|
674
705
|
};
|
|
675
706
|
}
|
|
676
707
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
action: Action,
|
|
682
|
-
duration: Duration,
|
|
683
|
-
parent: Optional(bool),
|
|
684
|
-
) []const Optional(Observation) {
|
|
685
|
-
if (parent == .true) return &OBS_NONE;
|
|
686
|
-
return switch (action.attacking) {
|
|
687
|
-
.None => &OBS_NONE,
|
|
688
|
-
.started => &OBS_STARTED,
|
|
689
|
-
else => if (duration.attacking >= 3)
|
|
690
|
-
&OBS_ENDED
|
|
691
|
-
else if (duration.attacking < 2)
|
|
692
|
-
&OBS_CONTINUING
|
|
693
|
-
else
|
|
694
|
-
&OBS,
|
|
695
|
-
};
|
|
696
|
-
}
|
|
708
|
+
const BND_NONE = [_]Optional(Observation(.binding)){.None};
|
|
709
|
+
const BND_STARTED = [_]Optional(Observation(.binding)){.started};
|
|
710
|
+
const BND_ENDED = [_]Optional(Observation(.binding)){.ended};
|
|
711
|
+
const BND = [_]Optional(Observation(.binding)){ .continuing, .ended };
|
|
697
712
|
|
|
698
713
|
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
699
714
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
@@ -701,12 +716,12 @@ pub const Rolls = struct {
|
|
|
701
716
|
action: Action,
|
|
702
717
|
duration: Duration,
|
|
703
718
|
parent: Optional(bool),
|
|
704
|
-
) []const Optional(Observation) {
|
|
705
|
-
if (parent == .true) return &
|
|
719
|
+
) []const Optional(Observation(.binding)) {
|
|
720
|
+
if (parent == .true) return &BND_NONE;
|
|
706
721
|
return switch (action.binding) {
|
|
707
|
-
.None => &
|
|
708
|
-
.started => &
|
|
709
|
-
else => if (duration.binding >= 4) &
|
|
722
|
+
.None => &BND_NONE,
|
|
723
|
+
.started => &BND_STARTED,
|
|
724
|
+
else => if (duration.binding >= 4) &BND_ENDED else &BND,
|
|
710
725
|
};
|
|
711
726
|
}
|
|
712
727
|
|
|
@@ -723,7 +738,7 @@ pub const Rolls = struct {
|
|
|
723
738
|
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
724
739
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
725
740
|
pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
726
|
-
if (parent == .false) return &BOOL_NONE;
|
|
741
|
+
if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
|
|
727
742
|
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
728
743
|
}
|
|
729
744
|
|
|
@@ -735,7 +750,7 @@ pub const Rolls = struct {
|
|
|
735
750
|
}
|
|
736
751
|
|
|
737
752
|
/// The min and max bounds on iteration over damage rolls.
|
|
738
|
-
pub const Range = struct { min:
|
|
753
|
+
pub const Range = struct { min: usize, max: usize };
|
|
739
754
|
|
|
740
755
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
741
756
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
@@ -748,7 +763,7 @@ pub const Rolls = struct {
|
|
|
748
763
|
|
|
749
764
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
750
765
|
/// given the base damage in `summaries`.
|
|
751
|
-
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool)
|
|
766
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
|
|
752
767
|
if (roll == 0) return roll;
|
|
753
768
|
|
|
754
769
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -760,9 +775,11 @@ pub const Rolls = struct {
|
|
|
760
775
|
|
|
761
776
|
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
762
777
|
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
763
|
-
pub fn confused(
|
|
764
|
-
|
|
765
|
-
|
|
778
|
+
pub fn confused(
|
|
779
|
+
action: Action,
|
|
780
|
+
parent: Optional(Observation(.confusion)),
|
|
781
|
+
) []const Optional(bool) {
|
|
782
|
+
const done = parent == .None or parent == .ended;
|
|
766
783
|
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
767
784
|
}
|
|
768
785
|
|
|
@@ -773,24 +790,24 @@ pub const Rolls = struct {
|
|
|
773
790
|
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
774
791
|
}
|
|
775
792
|
|
|
776
|
-
const SLOT_NONE = [_]
|
|
777
|
-
const SLOT = [_]
|
|
793
|
+
const SLOT_NONE = [_]u3{0};
|
|
794
|
+
const SLOT = [_]u3{ 1, 2, 3, 4 };
|
|
778
795
|
|
|
779
796
|
/// Returns a slice with a range of values for move slots given the `action` state
|
|
780
797
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
781
798
|
///
|
|
782
799
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
783
|
-
pub fn moveSlot(action: Action, parent: Optional(bool)) []const
|
|
800
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
|
|
784
801
|
if (parent == .false) return &SLOT_NONE;
|
|
785
802
|
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
786
803
|
}
|
|
787
804
|
|
|
788
|
-
const MULTI_NONE = [_]
|
|
789
|
-
const MULTI = [_]
|
|
805
|
+
const MULTI_NONE = [_]u3{0};
|
|
806
|
+
const MULTI = [_]u3{ 2, 3, 4, 5 };
|
|
790
807
|
|
|
791
808
|
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
792
809
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
793
|
-
pub fn multiHit(action: Action, parent: Optional(bool)) []const
|
|
810
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
|
|
794
811
|
if (parent == .false) return &MULTI_NONE;
|
|
795
812
|
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
796
813
|
}
|
|
@@ -859,12 +876,12 @@ test "Rolls.damage" {
|
|
|
859
876
|
|
|
860
877
|
test "Rolls.coalesce" {
|
|
861
878
|
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
862
|
-
try expectEqual(@as(u8, 0),
|
|
863
|
-
try expectEqual(@as(u8, 241),
|
|
864
|
-
try expectEqual(@as(u8, 255),
|
|
879
|
+
try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
|
|
880
|
+
try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
|
|
881
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
|
|
865
882
|
summaries.p1.damage.final = 74;
|
|
866
|
-
try expectEqual(@as(u8, 217),
|
|
867
|
-
try expectEqual(@as(u8, 255),
|
|
883
|
+
try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
|
|
884
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
|
|
868
885
|
}
|
|
869
886
|
|
|
870
887
|
test "Rolls.hit" {
|
|
@@ -893,15 +910,23 @@ test "Rolls.criticalHit" {
|
|
|
893
910
|
&.{ .false, .true },
|
|
894
911
|
Rolls.criticalHit(actions.p1, .None),
|
|
895
912
|
);
|
|
896
|
-
try expectEqualSlices(
|
|
913
|
+
try expectEqualSlices(
|
|
914
|
+
Optional(bool),
|
|
915
|
+
if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
|
|
916
|
+
Rolls.criticalHit(actions.p1, .false),
|
|
917
|
+
);
|
|
897
918
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
|
|
898
919
|
}
|
|
899
920
|
|
|
900
921
|
test "Rolls.confused" {
|
|
901
922
|
const actions: Actions = .{ .p2 = .{ .confused = .true } };
|
|
902
923
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
|
|
903
|
-
try expectEqualSlices(
|
|
904
|
-
|
|
924
|
+
try expectEqualSlices(
|
|
925
|
+
Optional(bool),
|
|
926
|
+
&.{ .false, .true },
|
|
927
|
+
Rolls.confused(actions.p2, .continuing),
|
|
928
|
+
);
|
|
929
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
|
|
905
930
|
}
|
|
906
931
|
|
|
907
932
|
test "Rolls.paralyzed" {
|
|
@@ -914,16 +939,16 @@ test "Rolls.paralyzed" {
|
|
|
914
939
|
|
|
915
940
|
test "Rolls.moveSlot" {
|
|
916
941
|
const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
|
|
917
|
-
try expectEqualSlices(
|
|
918
|
-
try expectEqualSlices(
|
|
919
|
-
try expectEqualSlices(
|
|
942
|
+
try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p1, .None));
|
|
943
|
+
try expectEqualSlices(u3, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
|
|
944
|
+
try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p2, .false));
|
|
920
945
|
}
|
|
921
946
|
|
|
922
947
|
test "Rolls.multiHit" {
|
|
923
948
|
const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
|
|
924
|
-
try expectEqualSlices(
|
|
925
|
-
try expectEqualSlices(
|
|
926
|
-
try expectEqualSlices(
|
|
949
|
+
try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p1, .None));
|
|
950
|
+
try expectEqualSlices(u3, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
|
|
951
|
+
try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p2, .false));
|
|
927
952
|
}
|
|
928
953
|
|
|
929
954
|
test "Rolls.metronome" {
|