@pkmn/engine 0.1.0-dev.565de616 → 0.1.0-dev.587596a6
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 +21 -13
- package/build/pkg/addon/browser.js +4 -1
- package/build/pkg/addon/browser.js.map +1 -1
- package/build/pkg/addon/node.js +20 -7
- package/build/pkg/addon/node.js.map +1 -1
- package/build/pkg/addon/wasm.js +41 -9
- package/build/pkg/addon/wasm.js.map +1 -1
- package/build/pkg/addon.d.ts +7 -7
- package/build/pkg/addon.js +8 -17
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/gen1.d.ts +5 -5
- package/build/pkg/gen1.js +28 -19
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +6 -97
- package/build/pkg/index.js +23 -75
- package/build/pkg/index.js.map +1 -1
- package/build/tools/debug.js +36 -9
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +18 -7
- package/build/tools/display/debug.jsx.map +1 -1
- package/build/tools/display/index.js +18 -7
- 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 +16 -1
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +181 -198
- package/package.json +31 -130
- package/src/bin/install-pkmn-engine +24 -8
- 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 +11 -21
- package/src/lib/common/js.zig +3 -9
- package/src/lib/common/optional.zig +11 -21
- package/src/lib/common/protocol.zig +176 -163
- package/src/lib/common/rational.zig +275 -284
- package/src/lib/common/util.zig +14 -31
- package/src/lib/gen1/calc.zig +65 -62
- package/src/lib/gen1/chance.zig +17 -32
- package/src/lib/gen1/data/types.zig +2 -2
- package/src/lib/gen1/data.zig +5 -5
- package/src/lib/gen1/helpers.zig +8 -10
- package/src/lib/gen1/mechanics.zig +32 -25
- package/src/lib/gen2/calc.zig +3 -6
- package/src/lib/gen2/chance.zig +12 -18
- package/src/lib/gen2/data/types.zig +6 -3
- package/src/lib/gen2/data.zig +8 -7
- package/src/lib/gen2/helpers.zig +4 -6
- package/src/lib/gen2/mechanics.zig +28 -17
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +3 -118
- package/src/lib/pkmn.zig +94 -19
- package/src/lib/wasm.zig +10 -9
- package/src/tools/display/dom.ts +5 -8
package/src/lib/common/util.zig
CHANGED
|
@@ -3,10 +3,8 @@ const std = @import("std");
|
|
|
3
3
|
const expect = std.testing.expect;
|
|
4
4
|
const expectEqual = std.testing.expectEqual;
|
|
5
5
|
|
|
6
|
-
const Pointer = if (@hasField(std.builtin.Type, "pointer")) .pointer else .Pointer;
|
|
7
|
-
|
|
8
6
|
pub fn PointerType(comptime P: type, comptime C: type) type {
|
|
9
|
-
return if (@
|
|
7
|
+
return if (@typeInfo(P).pointer.is_const) *const C else *C;
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
test PointerType {
|
|
@@ -17,7 +15,7 @@ test PointerType {
|
|
|
17
15
|
pub fn isPointerTo(p: anytype, comptime P: type) bool {
|
|
18
16
|
const info = @typeInfo(@TypeOf(p));
|
|
19
17
|
return switch (info) {
|
|
20
|
-
|
|
18
|
+
.pointer => info.pointer.child == P,
|
|
21
19
|
else => false,
|
|
22
20
|
};
|
|
23
21
|
}
|
|
@@ -29,33 +27,18 @@ test isPointerTo {
|
|
|
29
27
|
try expect(isPointerTo(&s, S));
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
pub fn repeat(comptime xs: anytype, comptime n: usize) [xs.len * n]@TypeOf(xs[0]) {
|
|
31
|
+
comptime {
|
|
32
|
+
const T = @TypeOf(xs[0]);
|
|
33
|
+
var ts: [xs.len * n]T = undefined;
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
var i: usize = 0;
|
|
36
|
+
while (i < n) : (i += 1) {
|
|
37
|
+
for (xs, 0..) |v, j| {
|
|
38
|
+
ts[i * xs.len + j] = v;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
comptime size: std.builtin.Type.Pointer.Size,
|
|
44
|
-
comptime child: type,
|
|
45
|
-
) type {
|
|
46
|
-
const info = @field(@typeInfo(source), @tagName(Pointer));
|
|
47
|
-
const args = .{
|
|
48
|
-
.size = size,
|
|
49
|
-
.is_const = info.is_const,
|
|
50
|
-
.is_volatile = info.is_volatile,
|
|
51
|
-
.is_allowzero = info.is_allowzero,
|
|
52
|
-
.alignment = info.alignment,
|
|
53
|
-
.address_space = info.address_space,
|
|
54
|
-
.child = child,
|
|
55
|
-
.sentinel = null,
|
|
56
|
-
};
|
|
57
|
-
return @Type(if (@hasField(std.builtin.Type, "pointer"))
|
|
58
|
-
.{ .pointer = args }
|
|
59
|
-
else
|
|
60
|
-
.{ .Pointer = args });
|
|
42
|
+
return ts;
|
|
43
|
+
}
|
|
61
44
|
}
|
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -35,9 +35,6 @@ const Sleeps = chance.Sleeps;
|
|
|
35
35
|
const tty = true; // DEBUG
|
|
36
36
|
const summary = false; // DEBUG
|
|
37
37
|
|
|
38
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
39
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
40
|
-
|
|
41
38
|
/// Information relevant to damage calculation that occured during a Generation I battle `update`.
|
|
42
39
|
pub const Summaries = extern struct {
|
|
43
40
|
/// Relevant information for Player 1.
|
|
@@ -99,13 +96,13 @@ pub const Calc = struct {
|
|
|
99
96
|
self: Calc,
|
|
100
97
|
player: Player,
|
|
101
98
|
comptime field: Action.Field,
|
|
102
|
-
)
|
|
99
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
103
100
|
if (!enabled) return null;
|
|
104
101
|
|
|
105
102
|
const val = @field(self.overrides.get(player), @tagName(field));
|
|
106
103
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
.@"enum" => val != .None,
|
|
105
|
+
.int => val != 0,
|
|
109
106
|
else => unreachable,
|
|
110
107
|
}) val else null;
|
|
111
108
|
}
|
|
@@ -146,7 +143,7 @@ const Null = struct {
|
|
|
146
143
|
self: Null,
|
|
147
144
|
player: Player,
|
|
148
145
|
comptime field: Action.Field,
|
|
149
|
-
)
|
|
146
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
150
147
|
_ = .{ self, player };
|
|
151
148
|
return null;
|
|
152
149
|
}
|
|
@@ -182,14 +179,15 @@ pub const Options = struct {
|
|
|
182
179
|
metronome: bool = false,
|
|
183
180
|
};
|
|
184
181
|
|
|
185
|
-
pub const MAX_FRONTIER =
|
|
182
|
+
pub const MAX_FRONTIER = 83;
|
|
186
183
|
|
|
187
184
|
pub fn transitions(
|
|
188
185
|
battle: anytype,
|
|
189
186
|
c1: Choice,
|
|
190
187
|
c2: Choice,
|
|
191
188
|
allocator: std.mem.Allocator,
|
|
192
|
-
|
|
189
|
+
out: *std.Io.Writer,
|
|
190
|
+
err: *std.Io.Writer,
|
|
193
191
|
options: Options,
|
|
194
192
|
) !?Stats {
|
|
195
193
|
var stats: Stats = .{};
|
|
@@ -198,8 +196,8 @@ pub fn transitions(
|
|
|
198
196
|
|
|
199
197
|
var seen = std.AutoHashMap(Actions, void).init(allocator);
|
|
200
198
|
defer seen.deinit();
|
|
201
|
-
var frontier
|
|
202
|
-
defer frontier.deinit();
|
|
199
|
+
var frontier: std.array_list.Aligned(Actions, null) = .empty;
|
|
200
|
+
defer frontier.deinit(allocator);
|
|
203
201
|
|
|
204
202
|
const d = options.durations;
|
|
205
203
|
|
|
@@ -216,8 +214,8 @@ pub fn transitions(
|
|
|
216
214
|
const p1 = b.side(.P1);
|
|
217
215
|
const p2 = b.side(.P2);
|
|
218
216
|
|
|
219
|
-
var p: Rational(
|
|
220
|
-
try frontier.append(opts.chance.actions);
|
|
217
|
+
var p: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
218
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
221
219
|
|
|
222
220
|
// zig fmt: off
|
|
223
221
|
for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
|
|
@@ -231,7 +229,9 @@ pub fn transitions(
|
|
|
231
229
|
const f = frontier.items[i];
|
|
232
230
|
const saved = stats.saved;
|
|
233
231
|
|
|
234
|
-
|
|
232
|
+
var r: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
233
|
+
|
|
234
|
+
try debug(out, f, .{
|
|
235
235
|
.shape = true,
|
|
236
236
|
.color = i,
|
|
237
237
|
.bold = true,
|
|
@@ -251,8 +251,8 @@ pub fn transitions(
|
|
|
251
251
|
for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
|
|
252
252
|
for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
|
|
253
253
|
for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
|
|
254
|
-
for (Rolls.confusion(f.p1, d.p1, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
255
|
-
for (Rolls.confusion(f.p2, d.p2, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
254
|
+
for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
255
|
+
for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
256
256
|
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
257
257
|
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
258
258
|
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
@@ -278,8 +278,8 @@ pub fn transitions(
|
|
|
278
278
|
|
|
279
279
|
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
280
280
|
|
|
281
|
-
const p1_min: u9 = p1_dmg.min;
|
|
282
|
-
const p2_min: u9 = p2_dmg.min;
|
|
281
|
+
const p1_min: u9 = @intCast(p1_dmg.min);
|
|
282
|
+
const p2_min: u9 = @intCast(p2_dmg.min);
|
|
283
283
|
|
|
284
284
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
285
285
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
@@ -296,16 +296,16 @@ pub fn transitions(
|
|
|
296
296
|
|
|
297
297
|
const summaries = &opts.calc.summaries;
|
|
298
298
|
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
299
|
-
p1_dmg.min
|
|
299
|
+
@intCast(p1_dmg.min)
|
|
300
300
|
else
|
|
301
|
-
|
|
301
|
+
Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
302
302
|
const p2_max: u9 =
|
|
303
|
-
|
|
303
|
+
Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
304
304
|
|
|
305
305
|
if (opts.chance.actions.matches(f)) {
|
|
306
306
|
if (!opts.chance.actions.relax().eql(a)) {
|
|
307
307
|
if (!summary) {
|
|
308
|
-
try debug(
|
|
308
|
+
try debug(out, opts.chance.actions, .{
|
|
309
309
|
.p1_max = p1_max,
|
|
310
310
|
.p2_max = p2_max,
|
|
311
311
|
.color = i,
|
|
@@ -319,7 +319,7 @@ pub fn transitions(
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
if (!summary) {
|
|
322
|
-
try debug(
|
|
322
|
+
try debug(out, opts.chance.actions, .{
|
|
323
323
|
.p1_max = p1_max,
|
|
324
324
|
.p2_max = p2_max,
|
|
325
325
|
.color = i,
|
|
@@ -332,7 +332,7 @@ pub fn transitions(
|
|
|
332
332
|
acts.p1.damage = @intCast(p1d);
|
|
333
333
|
acts.p2.damage = @intCast(p2d);
|
|
334
334
|
if ((try seen.getOrPut(acts)).found_existing) {
|
|
335
|
-
err
|
|
335
|
+
die(err, "already seen {f}", .{acts}, options.seed);
|
|
336
336
|
return error.TestUnexpectedResult;
|
|
337
337
|
}
|
|
338
338
|
}
|
|
@@ -343,25 +343,27 @@ pub fn transitions(
|
|
|
343
343
|
q.reduce();
|
|
344
344
|
try p.add(q);
|
|
345
345
|
p.reduce();
|
|
346
|
+
try r.add(q);
|
|
347
|
+
r.reduce();
|
|
346
348
|
|
|
347
349
|
stats.saved += 1;
|
|
348
350
|
|
|
349
351
|
if (p.q < p.p) {
|
|
350
|
-
err
|
|
352
|
+
die(err, "improper fraction {f}", .{p}, options.seed);
|
|
351
353
|
return error.TestUnexpectedResult;
|
|
352
354
|
}
|
|
353
355
|
} else {
|
|
354
356
|
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
355
|
-
try frontier.append(opts.chance.actions);
|
|
357
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
356
358
|
|
|
357
|
-
try debug(
|
|
359
|
+
try debug(out, opts.chance.actions, .{
|
|
358
360
|
.p1_max = p1_max,
|
|
359
361
|
.p2_max = p2_max,
|
|
360
362
|
.dim = true,
|
|
361
363
|
.newline = false,
|
|
362
364
|
});
|
|
363
|
-
try
|
|
364
|
-
try debug(
|
|
365
|
+
try out.writeAll(" → ");
|
|
366
|
+
try debug(out, opts.chance.actions, .{
|
|
365
367
|
.shape = true,
|
|
366
368
|
.color = frontier.items.len - 1,
|
|
367
369
|
.dim = true,
|
|
@@ -369,7 +371,7 @@ pub fn transitions(
|
|
|
369
371
|
.indent = false,
|
|
370
372
|
});
|
|
371
373
|
} else if (!summary) {
|
|
372
|
-
try debug(
|
|
374
|
+
try debug(out, opts.chance.actions, .{
|
|
373
375
|
.p1_max = p1_max,
|
|
374
376
|
.p2_max = p2_max,
|
|
375
377
|
.dim = true,
|
|
@@ -384,14 +386,12 @@ pub fn transitions(
|
|
|
384
386
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
385
387
|
|
|
386
388
|
assert(stats.saved > saved);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
);
|
|
394
|
-
}
|
|
389
|
+
// TODO: LLVM - float_from_int from 'u256' without intrinsics
|
|
390
|
+
// try out.print(
|
|
391
|
+
// " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
|
|
392
|
+
// .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
|
|
393
|
+
// p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
394
|
+
// );
|
|
395
395
|
|
|
396
396
|
}
|
|
397
397
|
|
|
@@ -406,7 +406,7 @@ pub fn transitions(
|
|
|
406
406
|
|
|
407
407
|
p.reduce();
|
|
408
408
|
if (p.p != 1 or p.q != 1) {
|
|
409
|
-
err
|
|
409
|
+
die(err, "expected 1, found {f}", .{p}, options.seed);
|
|
410
410
|
return error.TestExpectedEqual;
|
|
411
411
|
}
|
|
412
412
|
|
|
@@ -419,7 +419,8 @@ pub fn update(
|
|
|
419
419
|
c2: Choice,
|
|
420
420
|
options: anytype,
|
|
421
421
|
allocator: std.mem.Allocator,
|
|
422
|
-
|
|
422
|
+
out: *std.Io.Writer,
|
|
423
|
+
err: *std.Io.Writer,
|
|
423
424
|
transition: bool,
|
|
424
425
|
) !Result {
|
|
425
426
|
const durations = options.chance.durations;
|
|
@@ -437,8 +438,8 @@ pub fn update(
|
|
|
437
438
|
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
438
439
|
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
439
440
|
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
440
|
-
var
|
|
441
|
-
const n = try helpers.diff(&original, battle,
|
|
441
|
+
var pw: protocol.Writer = .{ .buffer = &buf };
|
|
442
|
+
const n = try helpers.diff(&original, battle, &pw);
|
|
442
443
|
|
|
443
444
|
// Applying the diff to the battle should take us back to the original copy
|
|
444
445
|
// of the battle (ignoring the RNG).
|
|
@@ -452,7 +453,7 @@ pub fn update(
|
|
|
452
453
|
// Ensure we can generate all transitions from the same original state
|
|
453
454
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
454
455
|
// the transitions function relies on RNG for discovery of states)
|
|
455
|
-
if (try transitions(unfix(original), c1, c2, allocator,
|
|
456
|
+
if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
|
|
456
457
|
.durations = durations,
|
|
457
458
|
.cap = true,
|
|
458
459
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
@@ -499,10 +500,9 @@ pub fn update(
|
|
|
499
500
|
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
500
501
|
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
501
502
|
error.TestExpectedEqual => {
|
|
502
|
-
std.debug.print("expected {}, found {}\n", .{ expected, actual });
|
|
503
|
+
std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
|
|
503
504
|
return e;
|
|
504
505
|
},
|
|
505
|
-
else => return e,
|
|
506
506
|
};
|
|
507
507
|
}
|
|
508
508
|
|
|
@@ -521,10 +521,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
521
521
|
};
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
fn
|
|
525
|
-
const w = std.io.getStdErr().writer();
|
|
524
|
+
fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
526
525
|
w.print(fmt, v) catch return;
|
|
527
|
-
if (seed) |s| return w.print("{}\n", .{s}) catch return;
|
|
526
|
+
if (seed) |s| return w.print("{d}\n", .{s}) catch return;
|
|
528
527
|
return w.writeByte('\n') catch return;
|
|
529
528
|
}
|
|
530
529
|
|
|
@@ -540,7 +539,7 @@ const Style = struct {
|
|
|
540
539
|
indent: bool = true,
|
|
541
540
|
};
|
|
542
541
|
|
|
543
|
-
fn debug(writer:
|
|
542
|
+
fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
|
|
544
543
|
if (style.indent) try writer.writeAll(" ");
|
|
545
544
|
if (tty) {
|
|
546
545
|
const mod: usize = if (style.dim) 2 else 1;
|
|
@@ -565,13 +564,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
|
|
|
565
564
|
if (style.newline) try writer.writeByte('\n');
|
|
566
565
|
}
|
|
567
566
|
|
|
568
|
-
fn format(writer:
|
|
567
|
+
fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
|
|
569
568
|
var input: [1024]u8 = undefined;
|
|
570
569
|
var output: [1024]u8 = undefined;
|
|
571
570
|
|
|
572
|
-
var
|
|
573
|
-
try actions.fmt(&
|
|
574
|
-
var len =
|
|
571
|
+
var w = std.Io.Writer.fixed(&input);
|
|
572
|
+
try actions.fmt(&w, false);
|
|
573
|
+
var len = w.buffered().len;
|
|
575
574
|
@memcpy(output[0..len], input[0..len]);
|
|
576
575
|
|
|
577
576
|
var find: [16]u8 = undefined;
|
|
@@ -644,7 +643,7 @@ pub const Rolls = struct {
|
|
|
644
643
|
};
|
|
645
644
|
}
|
|
646
645
|
|
|
647
|
-
/// FIXME
|
|
646
|
+
/// FIXME document
|
|
648
647
|
pub fn attacking(
|
|
649
648
|
action: Action,
|
|
650
649
|
duration: Duration,
|
|
@@ -668,11 +667,13 @@ pub const Rolls = struct {
|
|
|
668
667
|
const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
|
|
669
668
|
const CFZ_ENDED = [_]Optional(Confusion){.ended};
|
|
670
669
|
const CFZ = [_]Optional(Confusion){ .continuing, .ended };
|
|
670
|
+
const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
|
|
671
671
|
|
|
672
|
-
/// FIXME
|
|
672
|
+
/// FIXME document
|
|
673
673
|
pub fn confusion(
|
|
674
674
|
action: Action,
|
|
675
675
|
duration: Duration,
|
|
676
|
+
tie: Optional(Player),
|
|
676
677
|
sibling: Optional(Observation),
|
|
677
678
|
parent: Optional(Observation),
|
|
678
679
|
) []const Optional(Confusion) {
|
|
@@ -686,6 +687,8 @@ pub const Rolls = struct {
|
|
|
686
687
|
&CFZ_ENDED
|
|
687
688
|
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
688
689
|
&CFZ_CONTINUING
|
|
690
|
+
else if (tie != .None)
|
|
691
|
+
&CFZ_TIE
|
|
689
692
|
else
|
|
690
693
|
&CFZ,
|
|
691
694
|
};
|
|
@@ -731,7 +734,7 @@ pub const Rolls = struct {
|
|
|
731
734
|
}
|
|
732
735
|
|
|
733
736
|
/// The min and max bounds on iteration over damage rolls.
|
|
734
|
-
pub const Range = struct { min:
|
|
737
|
+
pub const Range = struct { min: usize, max: usize };
|
|
735
738
|
|
|
736
739
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
737
740
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
@@ -744,7 +747,7 @@ pub const Rolls = struct {
|
|
|
744
747
|
|
|
745
748
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
746
749
|
/// given the base damage in `summaries`.
|
|
747
|
-
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool)
|
|
750
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
|
|
748
751
|
if (roll == 0) return roll;
|
|
749
752
|
|
|
750
753
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -854,12 +857,12 @@ test "Rolls.damage" {
|
|
|
854
857
|
|
|
855
858
|
test "Rolls.coalesce" {
|
|
856
859
|
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
857
|
-
try expectEqual(@as(u8, 0),
|
|
858
|
-
try expectEqual(@as(u8, 241),
|
|
859
|
-
try expectEqual(@as(u8, 255),
|
|
860
|
+
try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
|
|
861
|
+
try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
|
|
862
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
|
|
860
863
|
summaries.p1.damage.final = 74;
|
|
861
|
-
try expectEqual(@as(u8, 217),
|
|
862
|
-
try expectEqual(@as(u8, 255),
|
|
864
|
+
try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
|
|
865
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
|
|
863
866
|
}
|
|
864
867
|
|
|
865
868
|
test "Rolls.hit" {
|
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -23,10 +23,6 @@ const PointerType = util.PointerType;
|
|
|
23
23
|
const print = std.debug.print;
|
|
24
24
|
const showdown = options.showdown;
|
|
25
25
|
|
|
26
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
27
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
28
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
29
|
-
|
|
30
26
|
/// Actions taken by a hypothetical "chance player" that convey information about which RNG events
|
|
31
27
|
/// were observed during a Generation I battle `update`. This can additionally be provided as input
|
|
32
28
|
/// to the `update` call to override the normal behavior of the RNG in order to force specific
|
|
@@ -56,15 +52,15 @@ pub const Actions = extern struct {
|
|
|
56
52
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
57
53
|
/// set to the same value).
|
|
58
54
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
59
|
-
inline for (@
|
|
60
|
-
inline for (@
|
|
55
|
+
inline for (@typeInfo(Actions).@"struct".fields) |player| {
|
|
56
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
61
57
|
const a_val = @field(@field(a, player.name), field.name);
|
|
62
58
|
const b_val = @field(@field(b, player.name), field.name);
|
|
63
59
|
|
|
64
60
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
65
|
-
|
|
61
|
+
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
66
62
|
return false,
|
|
67
|
-
|
|
63
|
+
.int => if ((a_val > 0) != (b_val > 0)) return false,
|
|
68
64
|
else => unreachable,
|
|
69
65
|
}
|
|
70
66
|
}
|
|
@@ -91,7 +87,7 @@ pub const Actions = extern struct {
|
|
|
91
87
|
return a;
|
|
92
88
|
}
|
|
93
89
|
|
|
94
|
-
pub fn fmt(self: Actions, writer:
|
|
90
|
+
pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
|
|
95
91
|
try writer.writeAll("<P1 = ");
|
|
96
92
|
try self.p1.fmt(writer, shape);
|
|
97
93
|
try writer.writeAll(", P2 = ");
|
|
@@ -99,8 +95,7 @@ pub const Actions = extern struct {
|
|
|
99
95
|
try writer.writeAll(">");
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
pub fn format(a: Actions,
|
|
103
|
-
_ = .{ f, o };
|
|
98
|
+
pub fn format(a: Actions, w: *std.Io.Writer) !void {
|
|
104
99
|
try fmt(a, w, false);
|
|
105
100
|
}
|
|
106
101
|
};
|
|
@@ -191,18 +186,17 @@ pub const Action = packed struct(u64) {
|
|
|
191
186
|
|
|
192
187
|
pub const Field = std.meta.FieldEnum(Action);
|
|
193
188
|
|
|
194
|
-
pub fn format(a: Action,
|
|
195
|
-
_ = .{ f, o };
|
|
189
|
+
pub fn format(a: Action, w: *std.Io.Writer) !void {
|
|
196
190
|
try fmt(a, w, false);
|
|
197
191
|
}
|
|
198
192
|
|
|
199
|
-
pub fn fmt(self: Action, writer:
|
|
193
|
+
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
200
194
|
try writer.writeByte('(');
|
|
201
195
|
var printed = false;
|
|
202
|
-
inline for (@
|
|
196
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
203
197
|
const val = @field(self, field.name);
|
|
204
198
|
switch (@typeInfo(@TypeOf(val))) {
|
|
205
|
-
|
|
199
|
+
.@"enum" => if (val != .None) {
|
|
206
200
|
if (printed) try writer.writeAll(", ");
|
|
207
201
|
if (shape) {
|
|
208
202
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -223,7 +217,7 @@ pub const Action = packed struct(u64) {
|
|
|
223
217
|
}
|
|
224
218
|
printed = true;
|
|
225
219
|
},
|
|
226
|
-
|
|
220
|
+
.int => if (val != 0) {
|
|
227
221
|
if (printed) try writer.writeAll(", ");
|
|
228
222
|
if (shape) {
|
|
229
223
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -256,14 +250,8 @@ pub const Durations = extern struct {
|
|
|
256
250
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
257
251
|
}
|
|
258
252
|
|
|
259
|
-
pub fn format(
|
|
260
|
-
self
|
|
261
|
-
comptime fmt: []const u8,
|
|
262
|
-
opts: std.fmt.FormatOptions,
|
|
263
|
-
writer: anytype,
|
|
264
|
-
) !void {
|
|
265
|
-
_ = .{ fmt, opts };
|
|
266
|
-
try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
|
|
253
|
+
pub fn format(self: Durations, writer: *std.Io.Writer) !void {
|
|
254
|
+
try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
|
|
267
255
|
}
|
|
268
256
|
};
|
|
269
257
|
|
|
@@ -281,17 +269,14 @@ pub const Duration = packed struct(u32) {
|
|
|
281
269
|
|
|
282
270
|
pub fn format(
|
|
283
271
|
self: Duration,
|
|
284
|
-
|
|
285
|
-
opts: std.fmt.FormatOptions,
|
|
286
|
-
writer: anytype,
|
|
272
|
+
writer: *std.Io.Writer,
|
|
287
273
|
) !void {
|
|
288
|
-
_ = .{ fmt, opts };
|
|
289
274
|
try writer.writeByte('(');
|
|
290
275
|
var printed = false;
|
|
291
|
-
inline for (@
|
|
276
|
+
inline for (@typeInfo(Duration).@"struct".fields) |field| {
|
|
292
277
|
const val = @field(self, field.name);
|
|
293
278
|
switch (@typeInfo(@TypeOf(val))) {
|
|
294
|
-
|
|
279
|
+
.int => if (val != 0) {
|
|
295
280
|
if (printed) try writer.writeAll(", ");
|
|
296
281
|
if (comptime std.mem.eql(u8, field.name, "sleeps")) {
|
|
297
282
|
try writer.print("{s}:[", .{field.name});
|
|
@@ -1067,7 +1052,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
|
1067
1052
|
|
|
1068
1053
|
r.reduce();
|
|
1069
1054
|
if (r.p != p or r.q != q) {
|
|
1070
|
-
print("expected {d}/{d}, found {}\n", .{ p, q, r });
|
|
1055
|
+
print("expected {d}/{d}, found {f}\n", .{ p, q, r });
|
|
1071
1056
|
return error.TestExpectedEqual;
|
|
1072
1057
|
}
|
|
1073
1058
|
}
|
|
@@ -98,7 +98,7 @@ pub const Type = enum(u4) {
|
|
|
98
98
|
return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
/// The precedence order of Type `
|
|
101
|
+
/// The precedence order of Type `t1` vs. Type `t2`.
|
|
102
102
|
pub fn precedence(t1: Type, t2: Type) u8 {
|
|
103
103
|
for (PRECEDENCE, 0..) |matchup, i| {
|
|
104
104
|
if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
|
|
@@ -108,7 +108,7 @@ pub const Type = enum(u4) {
|
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
/// Representation of a Pokémon's typing.
|
|
111
|
-
pub const Types = packed struct {
|
|
111
|
+
pub const Types = packed struct(u8) {
|
|
112
112
|
/// A Pokémon's primary type.
|
|
113
113
|
type1: Type = .Normal,
|
|
114
114
|
/// A Pokémon's secondary type (may be identical to its primary type).
|
package/src/lib/gen1/data.zig
CHANGED
|
@@ -62,7 +62,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
62
62
|
/// The last damage dealt by either side.
|
|
63
63
|
last_damage: u16 = 0,
|
|
64
64
|
/// Details about the last move selected/used by each player required to detect desyncs.
|
|
65
|
-
last_moves: [2]MoveDetails =
|
|
65
|
+
last_moves: [2]MoveDetails = @splat(.{}),
|
|
66
66
|
/// The pseudo random number generator.
|
|
67
67
|
rng: RNG,
|
|
68
68
|
|
|
@@ -122,12 +122,12 @@ test Battle {
|
|
|
122
122
|
/// Representation of one side of a Generation I Pokémon battle.
|
|
123
123
|
pub const Side = extern struct {
|
|
124
124
|
/// The player's party in its original order
|
|
125
|
-
pokemon: [6]Pokemon =
|
|
125
|
+
pokemon: [6]Pokemon = @splat(.{}),
|
|
126
126
|
/// The active Pokémon for the side, zero initialized if the battle has yet to start.
|
|
127
127
|
/// Note that fainted Pokémon are still consider "active" until their replacement switches in.
|
|
128
128
|
active: ActivePokemon = .{},
|
|
129
129
|
/// One-based slot indexes reflecting the current order of the player's party.
|
|
130
|
-
order: [6]u8 =
|
|
130
|
+
order: [6]u8 = @splat(0),
|
|
131
131
|
/// The last move the player selected.
|
|
132
132
|
last_selected_move: Move = .None,
|
|
133
133
|
/// The last move the player used.
|
|
@@ -166,7 +166,7 @@ pub const ActivePokemon = extern struct {
|
|
|
166
166
|
/// The active Pokémon's volatile statuses and associated data.
|
|
167
167
|
volatiles: Volatiles = .{},
|
|
168
168
|
/// The active Pokémon's current move slots.
|
|
169
|
-
moves: [4]MoveSlot =
|
|
169
|
+
moves: [4]MoveSlot = @splat(.{}),
|
|
170
170
|
|
|
171
171
|
comptime {
|
|
172
172
|
assert(@sizeOf(ActivePokemon) == 32);
|
|
@@ -186,7 +186,7 @@ pub const Pokemon = extern struct {
|
|
|
186
186
|
/// The Pokémon's unmodified stats.
|
|
187
187
|
stats: Stats(u16) = .{},
|
|
188
188
|
/// The Pokémon's original stored move slots.
|
|
189
|
-
moves: [4]MoveSlot =
|
|
189
|
+
moves: [4]MoveSlot = @splat(.{}),
|
|
190
190
|
/// The Pokémon's current HP.
|
|
191
191
|
hp: u16 = 0,
|
|
192
192
|
/// The Pokémon's current status.
|