@pkmn/engine 0.1.0-dev.645b4750 → 0.1.0-dev.6ba42cd5
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 +12 -31
- package/build/pkg/addon/wasm.js.map +1 -1
- package/build/pkg/addon.d.ts +4 -4
- package/build/pkg/addon.js.map +1 -1
- package/build/tools/debug.js +19 -1
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/ui.jsx +1 -1
- package/build/tools/display/ui.jsx.map +1 -1
- package/build.zig +135 -225
- package/package.json +23 -21
- package/src/bin/install-pkmn-engine +22 -7
- package/src/lib/bindings/c.zig +27 -82
- package/src/lib/bindings/node.zig +7 -11
- package/src/lib/bindings/wasm.zig +6 -15
- 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 +91 -173
- package/src/lib/common/util.zig +14 -46
- package/src/lib/gen1/calc.zig +46 -52
- 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 +23 -14
- 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 +9 -9
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +1 -5
- package/src/lib/pkmn.zig +91 -17
- package/src/lib/wasm.zig +2 -6
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
|
|
|
@@ -217,7 +215,7 @@ pub fn transitions(
|
|
|
217
215
|
const p2 = b.side(.P2);
|
|
218
216
|
|
|
219
217
|
var p: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
220
|
-
try frontier.append(opts.chance.actions);
|
|
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| {
|
|
@@ -233,7 +231,7 @@ pub fn transitions(
|
|
|
233
231
|
|
|
234
232
|
var r: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
235
233
|
|
|
236
|
-
try debug(
|
|
234
|
+
try debug(out, f, .{
|
|
237
235
|
.shape = true,
|
|
238
236
|
.color = i,
|
|
239
237
|
.bold = true,
|
|
@@ -280,8 +278,8 @@ pub fn transitions(
|
|
|
280
278
|
|
|
281
279
|
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
282
280
|
|
|
283
|
-
const p1_min: u9 = p1_dmg.min;
|
|
284
|
-
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);
|
|
285
283
|
|
|
286
284
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
287
285
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
@@ -298,7 +296,7 @@ pub fn transitions(
|
|
|
298
296
|
|
|
299
297
|
const summaries = &opts.calc.summaries;
|
|
300
298
|
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
301
|
-
p1_dmg.min
|
|
299
|
+
@intCast(p1_dmg.min)
|
|
302
300
|
else
|
|
303
301
|
Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
304
302
|
const p2_max: u9 =
|
|
@@ -307,7 +305,7 @@ pub fn transitions(
|
|
|
307
305
|
if (opts.chance.actions.matches(f)) {
|
|
308
306
|
if (!opts.chance.actions.relax().eql(a)) {
|
|
309
307
|
if (!summary) {
|
|
310
|
-
try debug(
|
|
308
|
+
try debug(out, opts.chance.actions, .{
|
|
311
309
|
.p1_max = p1_max,
|
|
312
310
|
.p2_max = p2_max,
|
|
313
311
|
.color = i,
|
|
@@ -321,7 +319,7 @@ pub fn transitions(
|
|
|
321
319
|
}
|
|
322
320
|
|
|
323
321
|
if (!summary) {
|
|
324
|
-
try debug(
|
|
322
|
+
try debug(out, opts.chance.actions, .{
|
|
325
323
|
.p1_max = p1_max,
|
|
326
324
|
.p2_max = p2_max,
|
|
327
325
|
.color = i,
|
|
@@ -334,7 +332,7 @@ pub fn transitions(
|
|
|
334
332
|
acts.p1.damage = @intCast(p1d);
|
|
335
333
|
acts.p2.damage = @intCast(p2d);
|
|
336
334
|
if ((try seen.getOrPut(acts)).found_existing) {
|
|
337
|
-
err
|
|
335
|
+
die(err, "already seen {f}", .{acts}, options.seed);
|
|
338
336
|
return error.TestUnexpectedResult;
|
|
339
337
|
}
|
|
340
338
|
}
|
|
@@ -351,21 +349,21 @@ pub fn transitions(
|
|
|
351
349
|
stats.saved += 1;
|
|
352
350
|
|
|
353
351
|
if (p.q < p.p) {
|
|
354
|
-
err
|
|
352
|
+
die(err, "improper fraction {f}", .{p}, options.seed);
|
|
355
353
|
return error.TestUnexpectedResult;
|
|
356
354
|
}
|
|
357
355
|
} else {
|
|
358
356
|
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
359
|
-
try frontier.append(opts.chance.actions);
|
|
357
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
360
358
|
|
|
361
|
-
try debug(
|
|
359
|
+
try debug(out, opts.chance.actions, .{
|
|
362
360
|
.p1_max = p1_max,
|
|
363
361
|
.p2_max = p2_max,
|
|
364
362
|
.dim = true,
|
|
365
363
|
.newline = false,
|
|
366
364
|
});
|
|
367
|
-
try
|
|
368
|
-
try debug(
|
|
365
|
+
try out.writeAll(" → ");
|
|
366
|
+
try debug(out, opts.chance.actions, .{
|
|
369
367
|
.shape = true,
|
|
370
368
|
.color = frontier.items.len - 1,
|
|
371
369
|
.dim = true,
|
|
@@ -373,7 +371,7 @@ pub fn transitions(
|
|
|
373
371
|
.indent = false,
|
|
374
372
|
});
|
|
375
373
|
} else if (!summary) {
|
|
376
|
-
try debug(
|
|
374
|
+
try debug(out, opts.chance.actions, .{
|
|
377
375
|
.p1_max = p1_max,
|
|
378
376
|
.p2_max = p2_max,
|
|
379
377
|
.dim = true,
|
|
@@ -388,15 +386,12 @@ pub fn transitions(
|
|
|
388
386
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
389
387
|
|
|
390
388
|
assert(stats.saved > saved);
|
|
391
|
-
// TODO:
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
// p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
398
|
-
// );
|
|
399
|
-
// }
|
|
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
|
+
// );
|
|
400
395
|
|
|
401
396
|
}
|
|
402
397
|
|
|
@@ -411,7 +406,7 @@ pub fn transitions(
|
|
|
411
406
|
|
|
412
407
|
p.reduce();
|
|
413
408
|
if (p.p != 1 or p.q != 1) {
|
|
414
|
-
err
|
|
409
|
+
die(err, "expected 1, found {f}", .{p}, options.seed);
|
|
415
410
|
return error.TestExpectedEqual;
|
|
416
411
|
}
|
|
417
412
|
|
|
@@ -424,7 +419,8 @@ pub fn update(
|
|
|
424
419
|
c2: Choice,
|
|
425
420
|
options: anytype,
|
|
426
421
|
allocator: std.mem.Allocator,
|
|
427
|
-
|
|
422
|
+
out: *std.Io.Writer,
|
|
423
|
+
err: *std.Io.Writer,
|
|
428
424
|
transition: bool,
|
|
429
425
|
) !Result {
|
|
430
426
|
const durations = options.chance.durations;
|
|
@@ -442,8 +438,8 @@ pub fn update(
|
|
|
442
438
|
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
443
439
|
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
444
440
|
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
445
|
-
var
|
|
446
|
-
const n = try helpers.diff(&original, battle,
|
|
441
|
+
var pw: protocol.Writer = .{ .buffer = &buf };
|
|
442
|
+
const n = try helpers.diff(&original, battle, &pw);
|
|
447
443
|
|
|
448
444
|
// Applying the diff to the battle should take us back to the original copy
|
|
449
445
|
// of the battle (ignoring the RNG).
|
|
@@ -457,7 +453,7 @@ pub fn update(
|
|
|
457
453
|
// Ensure we can generate all transitions from the same original state
|
|
458
454
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
459
455
|
// the transitions function relies on RNG for discovery of states)
|
|
460
|
-
if (try transitions(unfix(original), c1, c2, allocator,
|
|
456
|
+
if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
|
|
461
457
|
.durations = durations,
|
|
462
458
|
.cap = true,
|
|
463
459
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
@@ -504,10 +500,9 @@ pub fn update(
|
|
|
504
500
|
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
505
501
|
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
506
502
|
error.TestExpectedEqual => {
|
|
507
|
-
std.debug.print("expected {}, found {}\n", .{ expected, actual });
|
|
503
|
+
std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
|
|
508
504
|
return e;
|
|
509
505
|
},
|
|
510
|
-
else => return e,
|
|
511
506
|
};
|
|
512
507
|
}
|
|
513
508
|
|
|
@@ -526,10 +521,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
526
521
|
};
|
|
527
522
|
}
|
|
528
523
|
|
|
529
|
-
fn
|
|
530
|
-
const w = std.io.getStdErr().writer();
|
|
524
|
+
fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
531
525
|
w.print(fmt, v) catch return;
|
|
532
|
-
if (seed) |s| return w.print("{}\n", .{s}) catch return;
|
|
526
|
+
if (seed) |s| return w.print("{d}\n", .{s}) catch return;
|
|
533
527
|
return w.writeByte('\n') catch return;
|
|
534
528
|
}
|
|
535
529
|
|
|
@@ -545,7 +539,7 @@ const Style = struct {
|
|
|
545
539
|
indent: bool = true,
|
|
546
540
|
};
|
|
547
541
|
|
|
548
|
-
fn debug(writer:
|
|
542
|
+
fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
|
|
549
543
|
if (style.indent) try writer.writeAll(" ");
|
|
550
544
|
if (tty) {
|
|
551
545
|
const mod: usize = if (style.dim) 2 else 1;
|
|
@@ -570,13 +564,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
|
|
|
570
564
|
if (style.newline) try writer.writeByte('\n');
|
|
571
565
|
}
|
|
572
566
|
|
|
573
|
-
fn format(writer:
|
|
567
|
+
fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
|
|
574
568
|
var input: [1024]u8 = undefined;
|
|
575
569
|
var output: [1024]u8 = undefined;
|
|
576
570
|
|
|
577
|
-
var
|
|
578
|
-
try actions.fmt(&
|
|
579
|
-
var len =
|
|
571
|
+
var w = std.Io.Writer.fixed(&input);
|
|
572
|
+
try actions.fmt(&w, false);
|
|
573
|
+
var len = w.buffered().len;
|
|
580
574
|
@memcpy(output[0..len], input[0..len]);
|
|
581
575
|
|
|
582
576
|
var find: [16]u8 = undefined;
|
|
@@ -649,7 +643,7 @@ pub const Rolls = struct {
|
|
|
649
643
|
};
|
|
650
644
|
}
|
|
651
645
|
|
|
652
|
-
/// FIXME
|
|
646
|
+
/// FIXME document
|
|
653
647
|
pub fn attacking(
|
|
654
648
|
action: Action,
|
|
655
649
|
duration: Duration,
|
|
@@ -675,7 +669,7 @@ pub const Rolls = struct {
|
|
|
675
669
|
const CFZ = [_]Optional(Confusion){ .continuing, .ended };
|
|
676
670
|
const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
|
|
677
671
|
|
|
678
|
-
/// FIXME
|
|
672
|
+
/// FIXME document
|
|
679
673
|
pub fn confusion(
|
|
680
674
|
action: Action,
|
|
681
675
|
duration: Duration,
|
|
@@ -740,7 +734,7 @@ pub const Rolls = struct {
|
|
|
740
734
|
}
|
|
741
735
|
|
|
742
736
|
/// The min and max bounds on iteration over damage rolls.
|
|
743
|
-
pub const Range = struct { min:
|
|
737
|
+
pub const Range = struct { min: usize, max: usize };
|
|
744
738
|
|
|
745
739
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
746
740
|
/// the `parent` (whether the player's Pokémon's move 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.
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -19,8 +19,6 @@ const Stats = data.Stats;
|
|
|
19
19
|
|
|
20
20
|
const endian = builtin.cpu.arch.endian();
|
|
21
21
|
|
|
22
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
23
|
-
|
|
24
22
|
/// Configuration options controlling the domain of valid Pokémon that can be generated by the
|
|
25
23
|
/// random generator helpers.
|
|
26
24
|
pub const Options = struct {
|
|
@@ -163,7 +161,7 @@ pub const Pokemon = struct {
|
|
|
163
161
|
var pokemon = data.Pokemon{};
|
|
164
162
|
pokemon.species = p.species;
|
|
165
163
|
const species = Species.get(p.species);
|
|
166
|
-
inline for (@
|
|
164
|
+
inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
|
|
167
165
|
const hp = comptime std.mem.eql(u8, field.name, "hp");
|
|
168
166
|
const spc =
|
|
169
167
|
comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
|
|
@@ -199,7 +197,7 @@ pub const Pokemon = struct {
|
|
|
199
197
|
const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
|
|
200
198
|
var stats: Stats(u16) = .{};
|
|
201
199
|
const dvs = DVs.random(rand);
|
|
202
|
-
inline for (@
|
|
200
|
+
inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
|
|
203
201
|
@field(stats, field.name) = Stats(u16).calc(
|
|
204
202
|
field.name,
|
|
205
203
|
@field(species.stats, field.name),
|
|
@@ -212,7 +210,7 @@ pub const Pokemon = struct {
|
|
|
212
210
|
);
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
var ms
|
|
213
|
+
var ms: [4]MoveSlot = @splat(.{});
|
|
216
214
|
const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
|
|
217
215
|
for (0..n) |i| {
|
|
218
216
|
var m: Move = .None;
|
|
@@ -292,23 +290,23 @@ else
|
|
|
292
290
|
pub fn diff(
|
|
293
291
|
a: *const data.Battle(data.PRNG),
|
|
294
292
|
b: *const data.Battle(data.PRNG),
|
|
295
|
-
w: protocol.
|
|
293
|
+
w: *protocol.Writer,
|
|
296
294
|
) !usize {
|
|
297
|
-
const pos = w.
|
|
295
|
+
const pos = w.pos;
|
|
298
296
|
const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
|
|
299
297
|
assert(size % 2 == 0);
|
|
300
298
|
assert(size / 2 < 255);
|
|
301
299
|
|
|
302
300
|
var i: usize = 0;
|
|
303
301
|
while (i < size) : (i += 2) {
|
|
304
|
-
const val =
|
|
305
|
-
if (val !=
|
|
302
|
+
const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
|
|
303
|
+
if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
|
|
306
304
|
try w.writeByte(@intCast(i / 2));
|
|
307
305
|
try w.writeInt(u16, val, endian);
|
|
308
306
|
}
|
|
309
307
|
}
|
|
310
308
|
|
|
311
|
-
return w.
|
|
309
|
+
return w.pos - pos;
|
|
312
310
|
}
|
|
313
311
|
|
|
314
312
|
/// TODO
|
|
@@ -878,7 +878,7 @@ fn doMove(
|
|
|
878
878
|
var miss = showdown and miss: {
|
|
879
879
|
immune = move.target != .Self and !status and !counter and
|
|
880
880
|
(@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
|
|
881
|
-
|
|
881
|
+
@intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
|
|
882
882
|
if (immune and move.effect != .Binding) break :miss true;
|
|
883
883
|
if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
|
|
884
884
|
battle.last_damage = 0;
|
|
@@ -1019,7 +1019,7 @@ fn doMove(
|
|
|
1019
1019
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1020
1020
|
|
|
1021
1021
|
var nullified = false;
|
|
1022
|
-
var hit:
|
|
1022
|
+
var hit: usize = 0;
|
|
1023
1023
|
while (hit < hits) {
|
|
1024
1024
|
if (hit == 0) {
|
|
1025
1025
|
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
@@ -1047,7 +1047,7 @@ fn doMove(
|
|
|
1047
1047
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1048
1048
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1049
1049
|
}
|
|
1050
|
-
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1050
|
+
try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
|
|
1051
1051
|
} else if (showdown and move.effect == .Binding) {
|
|
1052
1052
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1053
1053
|
try Effects.binding(battle, player, rewrap, options);
|
|
@@ -1274,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1274
1274
|
// Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
|
|
1275
1275
|
const foe_last_selected_move =
|
|
1276
1276
|
Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1277
|
+
.Splash
|
|
1278
|
+
else
|
|
1279
|
+
foe.last_selected_move);
|
|
1280
1280
|
|
|
1281
1281
|
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1282
1282
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1283
1283
|
foe.last_selected_move != .Counter and
|
|
1284
1284
|
(foe_last_selected_move.type == .Normal or
|
|
1285
|
-
|
|
1285
|
+
foe_last_selected_move.type == .Fighting);
|
|
1286
1286
|
|
|
1287
1287
|
if (!used and !selected) {
|
|
1288
1288
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -1446,7 +1446,16 @@ fn moveHit(
|
|
|
1446
1446
|
break :miss true;
|
|
1447
1447
|
}
|
|
1448
1448
|
if (!showdown) {
|
|
1449
|
-
if (move.effect == .Swift)
|
|
1449
|
+
if (move.effect == .Swift) {
|
|
1450
|
+
// If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
|
|
1451
|
+
// Showdown, hence why the block above doesn't include this code), we need the
|
|
1452
|
+
// damage roll to be committed. However, the commit code will not save this roll if
|
|
1453
|
+
// the glitch occurs and the move was not marked as having hit. To work around this
|
|
1454
|
+
// we flag that the move hit (as Swift trivially does), but pass the probability of
|
|
1455
|
+
// the hit as zero to ensure it doesn't actually get saved.
|
|
1456
|
+
try options.chance.hit(player, true, 0);
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1450
1459
|
if (foe.active.volatiles.Invulnerable) break :miss true;
|
|
1451
1460
|
}
|
|
1452
1461
|
|
|
@@ -3136,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3136
3145
|
},
|
|
3137
3146
|
.Switch => {
|
|
3138
3147
|
const side = battle.side(player);
|
|
3139
|
-
var slot:
|
|
3148
|
+
var slot: usize = 2;
|
|
3140
3149
|
while (slot <= 6) : (slot += 1) {
|
|
3141
3150
|
const id = side.order[slot - 1];
|
|
3142
3151
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3143
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3152
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3144
3153
|
n += 1;
|
|
3145
3154
|
}
|
|
3146
3155
|
if (n == 0) {
|
|
@@ -3171,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3171
3180
|
return n;
|
|
3172
3181
|
}
|
|
3173
3182
|
|
|
3174
|
-
var slot:
|
|
3183
|
+
var slot: usize = 2;
|
|
3175
3184
|
while (slot <= 6) : (slot += 1) {
|
|
3176
3185
|
const id = side.order[slot - 1];
|
|
3177
3186
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3178
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3187
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3179
3188
|
n += 1;
|
|
3180
3189
|
}
|
|
3181
3190
|
|
|
@@ -3208,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3208
3217
|
const struggle =
|
|
3209
3218
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3210
3219
|
const s = if (struggle) 0 else slot;
|
|
3211
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3220
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3212
3221
|
n += 1;
|
|
3213
3222
|
return n;
|
|
3214
3223
|
}
|
|
@@ -3222,7 +3231,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3222
3231
|
if (m.id == .None) break;
|
|
3223
3232
|
if (m.pp == 0) continue;
|
|
3224
3233
|
if (active.volatiles.disable_move == slot) continue;
|
|
3225
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3234
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3226
3235
|
n += 1;
|
|
3227
3236
|
}
|
|
3228
3237
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|