@pkmn/engine 0.1.0-dev.0cabf16e → 0.1.0-dev.0e82a5ac
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 +28 -13
- 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/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 +55 -12
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +19 -94
- 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 +18 -8
- package/build/tools/display/ui.jsx.map +1 -1
- package/build/tools/display/util.js +140 -1
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +236 -216
- package/package.json +35 -131
- package/src/bin/install-pkmn-engine +25 -9
- package/src/lib/bindings/c.zig +200 -0
- package/src/lib/bindings/node.zig +117 -0
- package/src/lib/bindings/wasm.zig +50 -0
- package/src/lib/common/array.zig +5 -12
- package/src/lib/common/debug.zig +33 -59
- package/src/lib/common/js.zig +3 -9
- package/src/lib/common/optional.zig +11 -21
- 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 +14 -31
- package/src/lib/gen1/calc.zig +185 -182
- package/src/lib/gen1/chance.zig +146 -93
- 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 +12 -21
- package/src/lib/gen1/mechanics.zig +103 -78
- package/src/lib/gen2/calc.zig +12 -23
- package/src/lib/gen2/chance.zig +12 -18
- 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 +9 -18
- 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 +5 -8
- package/src/tools/display/ui.css +0 -5
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,13 +95,13 @@ 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
|
}
|
|
@@ -155,7 +142,7 @@ const Null = struct {
|
|
|
155
142
|
self: Null,
|
|
156
143
|
player: Player,
|
|
157
144
|
comptime field: Action.Field,
|
|
158
|
-
)
|
|
145
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
159
146
|
_ = .{ self, player };
|
|
160
147
|
return null;
|
|
161
148
|
}
|
|
@@ -191,30 +178,31 @@ pub const Options = struct {
|
|
|
191
178
|
metronome: bool = false,
|
|
192
179
|
};
|
|
193
180
|
|
|
194
|
-
pub const MAX_FRONTIER =
|
|
181
|
+
pub const MAX_FRONTIER = 83;
|
|
195
182
|
|
|
196
183
|
pub fn transitions(
|
|
197
184
|
battle: anytype,
|
|
198
185
|
c1: Choice,
|
|
199
186
|
c2: Choice,
|
|
200
187
|
allocator: std.mem.Allocator,
|
|
201
|
-
|
|
188
|
+
out: *std.Io.Writer,
|
|
189
|
+
err: *std.Io.Writer,
|
|
202
190
|
options: Options,
|
|
203
191
|
) !?Stats {
|
|
204
192
|
var stats: Stats = .{};
|
|
205
193
|
|
|
206
194
|
const cap = options.cap;
|
|
207
195
|
|
|
208
|
-
var seen
|
|
196
|
+
var seen: std.AutoHashMap(Actions, void) = .init(allocator);
|
|
209
197
|
defer seen.deinit();
|
|
210
|
-
var frontier
|
|
211
|
-
defer frontier.deinit();
|
|
198
|
+
var frontier: std.array_list.Aligned(Actions, null) = .empty;
|
|
199
|
+
defer frontier.deinit(allocator);
|
|
212
200
|
|
|
213
|
-
const
|
|
201
|
+
const d = options.durations;
|
|
214
202
|
|
|
215
203
|
var opts = pkmn.battle.options(
|
|
216
204
|
protocol.NULL,
|
|
217
|
-
Chance(Rational(u128)){ .probability = .{}, .durations =
|
|
205
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = d },
|
|
218
206
|
Calc{},
|
|
219
207
|
);
|
|
220
208
|
|
|
@@ -225,8 +213,8 @@ pub fn transitions(
|
|
|
225
213
|
const p1 = b.side(.P1);
|
|
226
214
|
const p2 = b.side(.P2);
|
|
227
215
|
|
|
228
|
-
var p: Rational(
|
|
229
|
-
try frontier.append(opts.chance.actions);
|
|
216
|
+
var p: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
217
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
230
218
|
|
|
231
219
|
// zig fmt: off
|
|
232
220
|
for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
|
|
@@ -240,7 +228,9 @@ pub fn transitions(
|
|
|
240
228
|
const f = frontier.items[i];
|
|
241
229
|
const saved = stats.saved;
|
|
242
230
|
|
|
243
|
-
|
|
231
|
+
var r: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
232
|
+
|
|
233
|
+
try debug(out, f, .{
|
|
244
234
|
.shape = true,
|
|
245
235
|
.color = i,
|
|
246
236
|
.bold = true,
|
|
@@ -254,20 +244,20 @@ pub fn transitions(
|
|
|
254
244
|
};
|
|
255
245
|
|
|
256
246
|
for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
257
|
-
for (Rolls.sleep(f.p1,
|
|
258
|
-
for (Rolls.sleep(f.p2,
|
|
259
|
-
for (Rolls.disable(f.p1,
|
|
260
|
-
for (Rolls.disable(f.p2,
|
|
261
|
-
for (Rolls.attacking(f.p1,
|
|
262
|
-
for (Rolls.attacking(f.p2,
|
|
263
|
-
for (Rolls.confusion(f.p1,
|
|
264
|
-
for (Rolls.confusion(f.p2,
|
|
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;
|
|
265
255
|
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
266
256
|
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
267
257
|
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
268
258
|
for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
269
|
-
for (Rolls.binding(f.p1,
|
|
270
|
-
for (Rolls.binding(f.p2,
|
|
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;
|
|
271
261
|
for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
272
262
|
for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
273
263
|
for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
@@ -287,15 +277,15 @@ pub fn transitions(
|
|
|
287
277
|
|
|
288
278
|
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
289
279
|
|
|
290
|
-
const p1_min: u9 = p1_dmg.min;
|
|
291
|
-
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);
|
|
292
282
|
|
|
293
283
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
294
284
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
295
285
|
|
|
296
286
|
opts.calc.overrides = a;
|
|
297
287
|
opts.calc.summaries = .{};
|
|
298
|
-
opts.chance = .{ .probability = .{}, .durations =
|
|
288
|
+
opts.chance = .{ .probability = .{}, .durations = d };
|
|
299
289
|
const q = &opts.chance.probability;
|
|
300
290
|
|
|
301
291
|
b = battle;
|
|
@@ -305,16 +295,16 @@ pub fn transitions(
|
|
|
305
295
|
|
|
306
296
|
const summaries = &opts.calc.summaries;
|
|
307
297
|
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
308
|
-
p1_dmg.min
|
|
298
|
+
@intCast(p1_dmg.min)
|
|
309
299
|
else
|
|
310
|
-
|
|
300
|
+
Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
311
301
|
const p2_max: u9 =
|
|
312
|
-
|
|
302
|
+
Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
313
303
|
|
|
314
304
|
if (opts.chance.actions.matches(f)) {
|
|
315
|
-
if (!
|
|
305
|
+
if (!opts.chance.actions.relax().eql(a)) {
|
|
316
306
|
if (!summary) {
|
|
317
|
-
try debug(
|
|
307
|
+
try debug(out, opts.chance.actions, .{
|
|
318
308
|
.p1_max = p1_max,
|
|
319
309
|
.p2_max = p2_max,
|
|
320
310
|
.color = i,
|
|
@@ -328,7 +318,7 @@ pub fn transitions(
|
|
|
328
318
|
}
|
|
329
319
|
|
|
330
320
|
if (!summary) {
|
|
331
|
-
try debug(
|
|
321
|
+
try debug(out, opts.chance.actions, .{
|
|
332
322
|
.p1_max = p1_max,
|
|
333
323
|
.p2_max = p2_max,
|
|
334
324
|
.color = i,
|
|
@@ -341,33 +331,38 @@ pub fn transitions(
|
|
|
341
331
|
acts.p1.damage = @intCast(p1d);
|
|
342
332
|
acts.p2.damage = @intCast(p2d);
|
|
343
333
|
if ((try seen.getOrPut(acts)).found_existing) {
|
|
344
|
-
err
|
|
334
|
+
die(err, "already seen {f}", .{acts}, options.seed);
|
|
345
335
|
return error.TestUnexpectedResult;
|
|
346
336
|
}
|
|
347
337
|
}
|
|
348
338
|
}
|
|
349
|
-
|
|
350
339
|
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
351
340
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
341
|
+
|
|
342
|
+
q.reduce();
|
|
352
343
|
try p.add(q);
|
|
344
|
+
p.reduce();
|
|
345
|
+
try r.add(q);
|
|
346
|
+
r.reduce();
|
|
347
|
+
|
|
353
348
|
stats.saved += 1;
|
|
354
349
|
|
|
355
350
|
if (p.q < p.p) {
|
|
356
|
-
err
|
|
351
|
+
die(err, "improper fraction {f}", .{p}, options.seed);
|
|
357
352
|
return error.TestUnexpectedResult;
|
|
358
353
|
}
|
|
359
354
|
} else {
|
|
360
|
-
if (!
|
|
361
|
-
try frontier.append(opts.chance.actions);
|
|
355
|
+
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
356
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
362
357
|
|
|
363
|
-
try debug(
|
|
358
|
+
try debug(out, opts.chance.actions, .{
|
|
364
359
|
.p1_max = p1_max,
|
|
365
360
|
.p2_max = p2_max,
|
|
366
361
|
.dim = true,
|
|
367
362
|
.newline = false,
|
|
368
363
|
});
|
|
369
|
-
try
|
|
370
|
-
try debug(
|
|
364
|
+
try out.writeAll(" → ");
|
|
365
|
+
try debug(out, opts.chance.actions, .{
|
|
371
366
|
.shape = true,
|
|
372
367
|
.color = frontier.items.len - 1,
|
|
373
368
|
.dim = true,
|
|
@@ -375,7 +370,7 @@ pub fn transitions(
|
|
|
375
370
|
.indent = false,
|
|
376
371
|
});
|
|
377
372
|
} else if (!summary) {
|
|
378
|
-
try debug(
|
|
373
|
+
try debug(out, opts.chance.actions, .{
|
|
379
374
|
.p1_max = p1_max,
|
|
380
375
|
.p2_max = p2_max,
|
|
381
376
|
.dim = true,
|
|
@@ -390,14 +385,12 @@ pub fn transitions(
|
|
|
390
385
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
391
386
|
|
|
392
387
|
assert(stats.saved > saved);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
);
|
|
400
|
-
}
|
|
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
|
+
// );
|
|
401
394
|
|
|
402
395
|
}
|
|
403
396
|
|
|
@@ -412,7 +405,7 @@ pub fn transitions(
|
|
|
412
405
|
|
|
413
406
|
p.reduce();
|
|
414
407
|
if (p.p != 1 or p.q != 1) {
|
|
415
|
-
err
|
|
408
|
+
die(err, "expected 1, found {f}", .{p}, options.seed);
|
|
416
409
|
return error.TestExpectedEqual;
|
|
417
410
|
}
|
|
418
411
|
|
|
@@ -425,7 +418,8 @@ pub fn update(
|
|
|
425
418
|
c2: Choice,
|
|
426
419
|
options: anytype,
|
|
427
420
|
allocator: std.mem.Allocator,
|
|
428
|
-
|
|
421
|
+
out: *std.Io.Writer,
|
|
422
|
+
err: *std.Io.Writer,
|
|
429
423
|
transition: bool,
|
|
430
424
|
) !Result {
|
|
431
425
|
const durations = options.chance.durations;
|
|
@@ -443,8 +437,8 @@ pub fn update(
|
|
|
443
437
|
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
444
438
|
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
445
439
|
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
446
|
-
var
|
|
447
|
-
const n = try helpers.diff(&original, battle,
|
|
440
|
+
var pw: protocol.Writer = .{ .buffer = &buf };
|
|
441
|
+
const n = try helpers.diff(&original, battle, &pw);
|
|
448
442
|
|
|
449
443
|
// Applying the diff to the battle should take us back to the original copy
|
|
450
444
|
// of the battle (ignoring the RNG).
|
|
@@ -458,7 +452,7 @@ pub fn update(
|
|
|
458
452
|
// Ensure we can generate all transitions from the same original state
|
|
459
453
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
460
454
|
// the transitions function relies on RNG for discovery of states)
|
|
461
|
-
if (try transitions(unfix(original), c1, c2, allocator,
|
|
455
|
+
if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
|
|
462
456
|
.durations = durations,
|
|
463
457
|
.cap = true,
|
|
464
458
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
@@ -505,10 +499,9 @@ pub fn update(
|
|
|
505
499
|
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
506
500
|
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
507
501
|
error.TestExpectedEqual => {
|
|
508
|
-
std.debug.print("expected {}, found {}\n", .{ expected, actual });
|
|
502
|
+
std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
|
|
509
503
|
return e;
|
|
510
504
|
},
|
|
511
|
-
else => return e,
|
|
512
505
|
};
|
|
513
506
|
}
|
|
514
507
|
|
|
@@ -527,27 +520,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
527
520
|
};
|
|
528
521
|
}
|
|
529
522
|
|
|
530
|
-
fn
|
|
531
|
-
if (!pkmn.options.overwrite) return actions;
|
|
532
|
-
var a = actions;
|
|
533
|
-
if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
|
|
534
|
-
if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
|
|
535
|
-
return a;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
539
|
-
for (frontier, 0..) |f, j| {
|
|
540
|
-
// TODO: is skipping this redundant check worth it?
|
|
541
|
-
if (i == j) continue;
|
|
542
|
-
if (f.matches(actions)) return true;
|
|
543
|
-
}
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
548
|
-
const w = std.io.getStdErr().writer();
|
|
523
|
+
fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
549
524
|
w.print(fmt, v) catch return;
|
|
550
|
-
if (seed) |s| return w.print("{}\n", .{s}) catch return;
|
|
525
|
+
if (seed) |s| return w.print("{d}\n", .{s}) catch return;
|
|
551
526
|
return w.writeByte('\n') catch return;
|
|
552
527
|
}
|
|
553
528
|
|
|
@@ -563,7 +538,7 @@ const Style = struct {
|
|
|
563
538
|
indent: bool = true,
|
|
564
539
|
};
|
|
565
540
|
|
|
566
|
-
fn debug(writer:
|
|
541
|
+
fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
|
|
567
542
|
if (style.indent) try writer.writeAll(" ");
|
|
568
543
|
if (tty) {
|
|
569
544
|
const mod: usize = if (style.dim) 2 else 1;
|
|
@@ -588,13 +563,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
|
|
|
588
563
|
if (style.newline) try writer.writeByte('\n');
|
|
589
564
|
}
|
|
590
565
|
|
|
591
|
-
fn format(writer:
|
|
566
|
+
fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
|
|
592
567
|
var input: [1024]u8 = undefined;
|
|
593
568
|
var output: [1024]u8 = undefined;
|
|
594
569
|
|
|
595
|
-
var
|
|
596
|
-
try actions.fmt(&
|
|
597
|
-
var len =
|
|
570
|
+
var w = std.Io.Writer.fixed(&input);
|
|
571
|
+
try actions.fmt(&w, false);
|
|
572
|
+
var len = w.buffered().len;
|
|
598
573
|
@memcpy(output[0..len], input[0..len]);
|
|
599
574
|
|
|
600
575
|
var find: [16]u8 = undefined;
|
|
@@ -627,88 +602,113 @@ pub const Rolls = struct {
|
|
|
627
602
|
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
628
603
|
}
|
|
629
604
|
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
const OBS = [_]Optional(Observation){ .continuing, .ended };
|
|
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 };
|
|
636
610
|
|
|
637
611
|
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
638
612
|
/// `durations`.
|
|
639
|
-
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
|
|
640
|
-
if (action.sleep == .None) return &
|
|
613
|
+
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation(.sleep)) {
|
|
614
|
+
if (action.sleep == .None) return &SLP_NONE;
|
|
641
615
|
return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
|
|
642
|
-
&
|
|
616
|
+
&SLP_ALL
|
|
643
617
|
else switch (action.sleep) {
|
|
644
|
-
.started => &
|
|
645
|
-
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &
|
|
618
|
+
.started => &SLP_STARTED,
|
|
619
|
+
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &SLP_ENDED else &SLP,
|
|
646
620
|
};
|
|
647
621
|
}
|
|
648
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
|
+
|
|
649
630
|
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
650
631
|
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
651
632
|
pub fn disable(
|
|
652
633
|
action: Action,
|
|
653
634
|
duration: Duration,
|
|
654
|
-
parent: Optional(Observation),
|
|
655
|
-
) []const Optional(Observation) {
|
|
656
|
-
if (action.disable == .None) return &
|
|
635
|
+
parent: Optional(Observation(.sleep)),
|
|
636
|
+
) []const Optional(Observation(.disable)) {
|
|
637
|
+
if (action.disable == .None) return &DIS_NONE;
|
|
657
638
|
return if (duration.disable == 0 and action.speed_tie != .None)
|
|
658
|
-
&
|
|
639
|
+
&DIS_ALL
|
|
659
640
|
else switch (action.disable) {
|
|
660
|
-
.started => &
|
|
641
|
+
.started => &DIS_STARTED,
|
|
661
642
|
else => if (duration.disable >= 8)
|
|
662
|
-
&
|
|
643
|
+
&DIS_ENDED
|
|
663
644
|
else if (parent != .None and parent != .started)
|
|
664
|
-
&
|
|
645
|
+
&DIS_CONTINUING
|
|
646
|
+
else
|
|
647
|
+
&DIS,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
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 };
|
|
656
|
+
|
|
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
|
|
665
670
|
else
|
|
666
|
-
&
|
|
671
|
+
&ATK,
|
|
667
672
|
};
|
|
668
673
|
}
|
|
669
674
|
|
|
670
|
-
const CFZ_NONE = [_]Optional(
|
|
671
|
-
const CFZ_STARTED = [_]Optional(
|
|
672
|
-
const CFZ_OVERWRITTEN = [_]Optional(
|
|
673
|
-
const CFZ_CONTINUING = [_]Optional(
|
|
674
|
-
const CFZ_ENDED = [_]Optional(
|
|
675
|
-
const CFZ = [_]Optional(
|
|
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 };
|
|
676
682
|
|
|
677
|
-
/// FIXME
|
|
683
|
+
/// FIXME document
|
|
678
684
|
pub fn confusion(
|
|
679
685
|
action: Action,
|
|
680
686
|
duration: Duration,
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
687
|
+
tie: Optional(Player),
|
|
688
|
+
sibling: Optional(Observation(.attacking)),
|
|
689
|
+
parent: Optional(Observation(.sleep)),
|
|
690
|
+
) []const Optional(Observation(.confusion)) {
|
|
684
691
|
return switch (action.confusion) {
|
|
685
692
|
.None => &CFZ_NONE,
|
|
686
|
-
.started => if (sibling == .ended)
|
|
693
|
+
.started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
|
|
687
694
|
&CFZ_OVERWRITTEN
|
|
688
695
|
else
|
|
689
696
|
&CFZ_STARTED,
|
|
690
697
|
else => if (duration.confusion >= 5)
|
|
691
698
|
&CFZ_ENDED
|
|
692
|
-
else if ((parent
|
|
699
|
+
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
693
700
|
&CFZ_CONTINUING
|
|
701
|
+
else if (tie != .None)
|
|
702
|
+
&CFZ_TIE
|
|
694
703
|
else
|
|
695
704
|
&CFZ,
|
|
696
705
|
};
|
|
697
706
|
}
|
|
698
707
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
.started => &OBS_STARTED,
|
|
704
|
-
else => if (duration.attacking >= 3)
|
|
705
|
-
&OBS_ENDED
|
|
706
|
-
else if (duration.attacking < 2)
|
|
707
|
-
&OBS_CONTINUING
|
|
708
|
-
else
|
|
709
|
-
&OBS,
|
|
710
|
-
};
|
|
711
|
-
}
|
|
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 };
|
|
712
712
|
|
|
713
713
|
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
714
714
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
@@ -716,12 +716,12 @@ pub const Rolls = struct {
|
|
|
716
716
|
action: Action,
|
|
717
717
|
duration: Duration,
|
|
718
718
|
parent: Optional(bool),
|
|
719
|
-
) []const Optional(Observation) {
|
|
720
|
-
if (parent == .true) return &
|
|
719
|
+
) []const Optional(Observation(.binding)) {
|
|
720
|
+
if (parent == .true) return &BND_NONE;
|
|
721
721
|
return switch (action.binding) {
|
|
722
|
-
.None => &
|
|
723
|
-
.started => &
|
|
724
|
-
else => if (duration.binding >= 4) &
|
|
722
|
+
.None => &BND_NONE,
|
|
723
|
+
.started => &BND_STARTED,
|
|
724
|
+
else => if (duration.binding >= 4) &BND_ENDED else &BND,
|
|
725
725
|
};
|
|
726
726
|
}
|
|
727
727
|
|
|
@@ -750,7 +750,7 @@ pub const Rolls = struct {
|
|
|
750
750
|
}
|
|
751
751
|
|
|
752
752
|
/// The min and max bounds on iteration over damage rolls.
|
|
753
|
-
pub const Range = struct { min:
|
|
753
|
+
pub const Range = struct { min: usize, max: usize };
|
|
754
754
|
|
|
755
755
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
756
756
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
@@ -763,7 +763,7 @@ pub const Rolls = struct {
|
|
|
763
763
|
|
|
764
764
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
765
765
|
/// given the base damage in `summaries`.
|
|
766
|
-
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool)
|
|
766
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
|
|
767
767
|
if (roll == 0) return roll;
|
|
768
768
|
|
|
769
769
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -775,7 +775,10 @@ pub const Rolls = struct {
|
|
|
775
775
|
|
|
776
776
|
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
777
777
|
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
778
|
-
pub fn confused(
|
|
778
|
+
pub fn confused(
|
|
779
|
+
action: Action,
|
|
780
|
+
parent: Optional(Observation(.confusion)),
|
|
781
|
+
) []const Optional(bool) {
|
|
779
782
|
const done = parent == .None or parent == .ended;
|
|
780
783
|
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
781
784
|
}
|
|
@@ -787,24 +790,24 @@ pub const Rolls = struct {
|
|
|
787
790
|
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
788
791
|
}
|
|
789
792
|
|
|
790
|
-
const SLOT_NONE = [_]
|
|
791
|
-
const SLOT = [_]
|
|
793
|
+
const SLOT_NONE = [_]u3{0};
|
|
794
|
+
const SLOT = [_]u3{ 1, 2, 3, 4 };
|
|
792
795
|
|
|
793
796
|
/// Returns a slice with a range of values for move slots given the `action` state
|
|
794
797
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
795
798
|
///
|
|
796
799
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
797
|
-
pub fn moveSlot(action: Action, parent: Optional(bool)) []const
|
|
800
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
|
|
798
801
|
if (parent == .false) return &SLOT_NONE;
|
|
799
802
|
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
800
803
|
}
|
|
801
804
|
|
|
802
|
-
const MULTI_NONE = [_]
|
|
803
|
-
const MULTI = [_]
|
|
805
|
+
const MULTI_NONE = [_]u3{0};
|
|
806
|
+
const MULTI = [_]u3{ 2, 3, 4, 5 };
|
|
804
807
|
|
|
805
808
|
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
806
809
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
807
|
-
pub fn multiHit(action: Action, parent: Optional(bool)) []const
|
|
810
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
|
|
808
811
|
if (parent == .false) return &MULTI_NONE;
|
|
809
812
|
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
810
813
|
}
|
|
@@ -873,12 +876,12 @@ test "Rolls.damage" {
|
|
|
873
876
|
|
|
874
877
|
test "Rolls.coalesce" {
|
|
875
878
|
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
876
|
-
try expectEqual(@as(u8, 0),
|
|
877
|
-
try expectEqual(@as(u8, 241),
|
|
878
|
-
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));
|
|
879
882
|
summaries.p1.damage.final = 74;
|
|
880
|
-
try expectEqual(@as(u8, 217),
|
|
881
|
-
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));
|
|
882
885
|
}
|
|
883
886
|
|
|
884
887
|
test "Rolls.hit" {
|
|
@@ -936,16 +939,16 @@ test "Rolls.paralyzed" {
|
|
|
936
939
|
|
|
937
940
|
test "Rolls.moveSlot" {
|
|
938
941
|
const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
|
|
939
|
-
try expectEqualSlices(
|
|
940
|
-
try expectEqualSlices(
|
|
941
|
-
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));
|
|
942
945
|
}
|
|
943
946
|
|
|
944
947
|
test "Rolls.multiHit" {
|
|
945
948
|
const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
|
|
946
|
-
try expectEqualSlices(
|
|
947
|
-
try expectEqualSlices(
|
|
948
|
-
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));
|
|
949
952
|
}
|
|
950
953
|
|
|
951
954
|
test "Rolls.metronome" {
|