@pkmn/engine 0.1.0-dev.3393edb3 → 0.1.0-dev.3846c8e6
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 +20 -9
- 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 -54
- 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 +205 -189
- 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 +112 -93
- 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 +46 -34
- package/src/tools/display/ui.css +8 -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,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,91 +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
|
|
623
|
-
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 };
|
|
624
610
|
|
|
625
611
|
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
626
612
|
/// `durations`.
|
|
627
|
-
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
|
|
628
|
-
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;
|
|
629
615
|
return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
|
|
630
|
-
&
|
|
616
|
+
&SLP_ALL
|
|
631
617
|
else switch (action.sleep) {
|
|
632
|
-
.started => &
|
|
633
|
-
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,
|
|
634
620
|
};
|
|
635
621
|
}
|
|
636
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
|
+
|
|
637
630
|
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
638
631
|
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
639
632
|
pub fn disable(
|
|
640
633
|
action: Action,
|
|
641
634
|
duration: Duration,
|
|
642
|
-
parent: Optional(Observation),
|
|
643
|
-
) []const Optional(Observation) {
|
|
644
|
-
if (action.disable == .None) return &
|
|
635
|
+
parent: Optional(Observation(.sleep)),
|
|
636
|
+
) []const Optional(Observation(.disable)) {
|
|
637
|
+
if (action.disable == .None) return &DIS_NONE;
|
|
645
638
|
return if (duration.disable == 0 and action.speed_tie != .None)
|
|
646
|
-
&
|
|
639
|
+
&DIS_ALL
|
|
647
640
|
else switch (action.disable) {
|
|
648
|
-
.started => &
|
|
641
|
+
.started => &DIS_STARTED,
|
|
649
642
|
else => if (duration.disable >= 8)
|
|
650
|
-
&
|
|
643
|
+
&DIS_ENDED
|
|
651
644
|
else if (parent != .None and parent != .started)
|
|
652
|
-
&
|
|
645
|
+
&DIS_CONTINUING
|
|
653
646
|
else
|
|
654
|
-
&
|
|
647
|
+
&DIS,
|
|
655
648
|
};
|
|
656
649
|
}
|
|
657
650
|
|
|
658
|
-
const
|
|
659
|
-
const
|
|
660
|
-
const
|
|
661
|
-
const
|
|
662
|
-
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 };
|
|
663
656
|
|
|
664
|
-
///
|
|
665
|
-
|
|
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
|
|
666
684
|
pub fn confusion(
|
|
667
685
|
action: Action,
|
|
668
686
|
duration: Duration,
|
|
669
|
-
|
|
670
|
-
|
|
687
|
+
tie: Optional(Player),
|
|
688
|
+
sibling: Optional(Observation(.attacking)),
|
|
689
|
+
parent: Optional(Observation(.sleep)),
|
|
690
|
+
) []const Optional(Observation(.confusion)) {
|
|
671
691
|
return switch (action.confusion) {
|
|
672
692
|
.None => &CFZ_NONE,
|
|
673
|
-
.started =>
|
|
693
|
+
.started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
|
|
694
|
+
&CFZ_OVERWRITTEN
|
|
695
|
+
else
|
|
696
|
+
&CFZ_STARTED,
|
|
674
697
|
else => if (duration.confusion >= 5)
|
|
675
698
|
&CFZ_ENDED
|
|
676
|
-
else if ((parent
|
|
699
|
+
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
677
700
|
&CFZ_CONTINUING
|
|
701
|
+
else if (tie != .None)
|
|
702
|
+
&CFZ_TIE
|
|
678
703
|
else
|
|
679
704
|
&CFZ,
|
|
680
705
|
};
|
|
681
706
|
}
|
|
682
707
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
action: Action,
|
|
688
|
-
duration: Duration,
|
|
689
|
-
parent: Optional(bool),
|
|
690
|
-
) []const Optional(Observation) {
|
|
691
|
-
if (parent == .true) return &OBS_NONE;
|
|
692
|
-
return switch (action.attacking) {
|
|
693
|
-
.None => &OBS_NONE,
|
|
694
|
-
.started => &OBS_STARTED,
|
|
695
|
-
else => if (duration.attacking >= 3)
|
|
696
|
-
&OBS_ENDED
|
|
697
|
-
else if (duration.attacking < 2)
|
|
698
|
-
&OBS_CONTINUING
|
|
699
|
-
else
|
|
700
|
-
&OBS,
|
|
701
|
-
};
|
|
702
|
-
}
|
|
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 };
|
|
703
712
|
|
|
704
713
|
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
705
714
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
@@ -707,12 +716,12 @@ pub const Rolls = struct {
|
|
|
707
716
|
action: Action,
|
|
708
717
|
duration: Duration,
|
|
709
718
|
parent: Optional(bool),
|
|
710
|
-
) []const Optional(Observation) {
|
|
711
|
-
if (parent == .true) return &
|
|
719
|
+
) []const Optional(Observation(.binding)) {
|
|
720
|
+
if (parent == .true) return &BND_NONE;
|
|
712
721
|
return switch (action.binding) {
|
|
713
|
-
.None => &
|
|
714
|
-
.started => &
|
|
715
|
-
else => if (duration.binding >= 4) &
|
|
722
|
+
.None => &BND_NONE,
|
|
723
|
+
.started => &BND_STARTED,
|
|
724
|
+
else => if (duration.binding >= 4) &BND_ENDED else &BND,
|
|
716
725
|
};
|
|
717
726
|
}
|
|
718
727
|
|
|
@@ -741,7 +750,7 @@ pub const Rolls = struct {
|
|
|
741
750
|
}
|
|
742
751
|
|
|
743
752
|
/// The min and max bounds on iteration over damage rolls.
|
|
744
|
-
pub const Range = struct { min:
|
|
753
|
+
pub const Range = struct { min: usize, max: usize };
|
|
745
754
|
|
|
746
755
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
747
756
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
@@ -754,7 +763,7 @@ pub const Rolls = struct {
|
|
|
754
763
|
|
|
755
764
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
756
765
|
/// given the base damage in `summaries`.
|
|
757
|
-
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool)
|
|
766
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
|
|
758
767
|
if (roll == 0) return roll;
|
|
759
768
|
|
|
760
769
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -766,7 +775,10 @@ pub const Rolls = struct {
|
|
|
766
775
|
|
|
767
776
|
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
768
777
|
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
769
|
-
pub fn confused(
|
|
778
|
+
pub fn confused(
|
|
779
|
+
action: Action,
|
|
780
|
+
parent: Optional(Observation(.confusion)),
|
|
781
|
+
) []const Optional(bool) {
|
|
770
782
|
const done = parent == .None or parent == .ended;
|
|
771
783
|
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
772
784
|
}
|
|
@@ -778,24 +790,24 @@ pub const Rolls = struct {
|
|
|
778
790
|
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
779
791
|
}
|
|
780
792
|
|
|
781
|
-
const SLOT_NONE = [_]
|
|
782
|
-
const SLOT = [_]
|
|
793
|
+
const SLOT_NONE = [_]u3{0};
|
|
794
|
+
const SLOT = [_]u3{ 1, 2, 3, 4 };
|
|
783
795
|
|
|
784
796
|
/// Returns a slice with a range of values for move slots given the `action` state
|
|
785
797
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
786
798
|
///
|
|
787
799
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
788
|
-
pub fn moveSlot(action: Action, parent: Optional(bool)) []const
|
|
800
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
|
|
789
801
|
if (parent == .false) return &SLOT_NONE;
|
|
790
802
|
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
791
803
|
}
|
|
792
804
|
|
|
793
|
-
const MULTI_NONE = [_]
|
|
794
|
-
const MULTI = [_]
|
|
805
|
+
const MULTI_NONE = [_]u3{0};
|
|
806
|
+
const MULTI = [_]u3{ 2, 3, 4, 5 };
|
|
795
807
|
|
|
796
808
|
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
797
809
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
798
|
-
pub fn multiHit(action: Action, parent: Optional(bool)) []const
|
|
810
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
|
|
799
811
|
if (parent == .false) return &MULTI_NONE;
|
|
800
812
|
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
801
813
|
}
|
|
@@ -864,12 +876,12 @@ test "Rolls.damage" {
|
|
|
864
876
|
|
|
865
877
|
test "Rolls.coalesce" {
|
|
866
878
|
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
867
|
-
try expectEqual(@as(u8, 0),
|
|
868
|
-
try expectEqual(@as(u8, 241),
|
|
869
|
-
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));
|
|
870
882
|
summaries.p1.damage.final = 74;
|
|
871
|
-
try expectEqual(@as(u8, 217),
|
|
872
|
-
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));
|
|
873
885
|
}
|
|
874
886
|
|
|
875
887
|
test "Rolls.hit" {
|
|
@@ -909,7 +921,11 @@ test "Rolls.criticalHit" {
|
|
|
909
921
|
test "Rolls.confused" {
|
|
910
922
|
const actions: Actions = .{ .p2 = .{ .confused = .true } };
|
|
911
923
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
|
|
912
|
-
try expectEqualSlices(
|
|
924
|
+
try expectEqualSlices(
|
|
925
|
+
Optional(bool),
|
|
926
|
+
&.{ .false, .true },
|
|
927
|
+
Rolls.confused(actions.p2, .continuing),
|
|
928
|
+
);
|
|
913
929
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
|
|
914
930
|
}
|
|
915
931
|
|
|
@@ -923,16 +939,16 @@ test "Rolls.paralyzed" {
|
|
|
923
939
|
|
|
924
940
|
test "Rolls.moveSlot" {
|
|
925
941
|
const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
|
|
926
|
-
try expectEqualSlices(
|
|
927
|
-
try expectEqualSlices(
|
|
928
|
-
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));
|
|
929
945
|
}
|
|
930
946
|
|
|
931
947
|
test "Rolls.multiHit" {
|
|
932
948
|
const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
|
|
933
|
-
try expectEqualSlices(
|
|
934
|
-
try expectEqualSlices(
|
|
935
|
-
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));
|
|
936
952
|
}
|
|
937
953
|
|
|
938
954
|
test "Rolls.metronome" {
|