@pkmn/engine 0.1.0-dev.3a62fd35 → 0.1.0-dev.3de821b9
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 -3
- package/README.md +13 -11
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +2 -3
- package/build/pkg/protocol.js +4 -4
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +8 -3
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +2 -3
- package/build/tools/debug.js.map +1 -1
- package/build.zig +32 -50
- package/package.json +24 -17
- package/src/bin/install-pkmn-engine +13 -5
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +11 -9
- package/src/lib/common/rational.zig +102 -12
- package/src/lib/common/rng.zig +6 -6
- package/src/lib/gen1/calc.zig +480 -92
- package/src/lib/gen1/chance.zig +384 -268
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +3 -3
- package/src/lib/gen1/helpers.zig +90 -534
- package/src/lib/gen1/mechanics.zig +266 -158
- package/src/lib/gen2/chance.zig +8 -440
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +119 -61
- package/src/lib/pkmn.zig +4 -12
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -329
package/src/lib/gen2/chance.zig
CHANGED
|
@@ -42,12 +42,6 @@ pub const Actions = extern struct {
|
|
|
42
42
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/// Perform a reset by clearing fields which should not persist across updates.
|
|
46
|
-
pub fn reset(self: *Actions) void {
|
|
47
|
-
self.p1.reset();
|
|
48
|
-
self.p2.reset();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
45
|
/// Returns true if `a` is equal to `b`.
|
|
52
46
|
pub fn eql(a: Actions, b: Actions) bool {
|
|
53
47
|
return @as(u128, @bitCast(a.p1)) == @as(u128, @bitCast(b.p1)) and
|
|
@@ -64,15 +58,6 @@ pub const Actions = extern struct {
|
|
|
64
58
|
const b_val = @field(@field(b, player.name), field.name);
|
|
65
59
|
|
|
66
60
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
67
|
-
.Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
|
|
68
|
-
const a_val_f = @field(a_val, f.name);
|
|
69
|
-
const b_val_f = @field(b_val, f.name);
|
|
70
|
-
switch (@typeInfo(@TypeOf(a_val_f))) {
|
|
71
|
-
.Int => if ((a_val_f > 0) != (b_val_f > 0)) return false,
|
|
72
|
-
.Bool => if (a_val_f != b_val_f) return false,
|
|
73
|
-
else => unreachable,
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
61
|
.Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
77
62
|
return false,
|
|
78
63
|
.Int => if ((a_val > 0) != (b_val > 0)) return false,
|
|
@@ -97,16 +82,11 @@ pub const Actions = extern struct {
|
|
|
97
82
|
}
|
|
98
83
|
};
|
|
99
84
|
|
|
85
|
+
// TODO: ziglang/zig#19730
|
|
100
86
|
test Actions {
|
|
101
87
|
const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
|
|
102
88
|
const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
|
|
103
89
|
const c: Actions = .{ .p1 = .{ .hit = .true } };
|
|
104
|
-
const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
|
|
105
|
-
const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
106
|
-
const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
107
|
-
const g: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1 } } };
|
|
108
|
-
const h: Actions =
|
|
109
|
-
.{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1, .thrash = true } } };
|
|
110
90
|
|
|
111
91
|
try expect(a.eql(a));
|
|
112
92
|
try expect(!a.eql(b));
|
|
@@ -119,10 +99,6 @@ test Actions {
|
|
|
119
99
|
try expect(b.matches(a));
|
|
120
100
|
try expect(!a.matches(c));
|
|
121
101
|
try expect(!c.matches(a));
|
|
122
|
-
try expect(d.matches(e));
|
|
123
|
-
try expect(!d.matches(f));
|
|
124
|
-
try expect(!g.matches(h));
|
|
125
|
-
try expect(h.matches(h));
|
|
126
102
|
}
|
|
127
103
|
|
|
128
104
|
/// Information about the RNG that was observed during a Generation II battle `update` for a
|
|
@@ -188,20 +164,10 @@ pub const Action = packed struct(u128) {
|
|
|
188
164
|
/// If not None, the Move to return for Rolls.metronome.
|
|
189
165
|
metronome: Move = .None,
|
|
190
166
|
|
|
191
|
-
_:
|
|
192
|
-
|
|
193
|
-
/// Observed values of various durations. Does not influence future RNG calls. TODO
|
|
194
|
-
durations: Duration = .{},
|
|
195
|
-
|
|
196
|
-
pub const DURATIONS: u128 = 0xFFFFFFFF00000000_0000000000000000;
|
|
167
|
+
_: u48 = 0, // TODO
|
|
197
168
|
|
|
198
169
|
pub const Field = std.meta.FieldEnum(Action);
|
|
199
170
|
|
|
200
|
-
/// Perform a reset by clearing fields which should not persist across updates.
|
|
201
|
-
pub fn reset(self: *Action) void {
|
|
202
|
-
self.* = @bitCast(@as(u128, @bitCast(self.*)) & DURATIONS);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
171
|
pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
|
|
206
172
|
_ = .{ f, o };
|
|
207
173
|
try fmt(a, w, false);
|
|
@@ -213,18 +179,6 @@ pub const Action = packed struct(u128) {
|
|
|
213
179
|
inline for (@typeInfo(Action).Struct.fields) |field| {
|
|
214
180
|
const val = @field(self, field.name);
|
|
215
181
|
switch (@typeInfo(@TypeOf(val))) {
|
|
216
|
-
.Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
|
|
217
|
-
const v = @field(val, f.name);
|
|
218
|
-
if (v != 0) {
|
|
219
|
-
if (printed) try writer.writeAll(", ");
|
|
220
|
-
if (shape) {
|
|
221
|
-
try writer.print("{s}:?", .{f.name});
|
|
222
|
-
} else {
|
|
223
|
-
try writer.print("{s}:{d}", .{ f.name, v });
|
|
224
|
-
}
|
|
225
|
-
printed = true;
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
182
|
.Enum => if (val != .None) {
|
|
229
183
|
if (printed) try writer.writeAll(", ");
|
|
230
184
|
if (shape) {
|
|
@@ -255,36 +209,6 @@ pub const Action = packed struct(u128) {
|
|
|
255
209
|
}
|
|
256
210
|
};
|
|
257
211
|
|
|
258
|
-
test Action {
|
|
259
|
-
var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
|
|
260
|
-
a.reset();
|
|
261
|
-
|
|
262
|
-
try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/// Observed values for various durations that need to be tracked in order to properly
|
|
266
|
-
/// deduplicate transitions with a primary key. TODO
|
|
267
|
-
pub const Duration = packed struct(u32) {
|
|
268
|
-
/// The number of turns a Pokémon has been observed to be sleeping.
|
|
269
|
-
sleep: u3 = 0,
|
|
270
|
-
/// The number of turns a Pokémon has been observed to be confused.
|
|
271
|
-
confusion: u3 = 0,
|
|
272
|
-
/// TODO
|
|
273
|
-
thrash: bool = false,
|
|
274
|
-
/// The number of turns a Pokémon has been observed to be disabled.
|
|
275
|
-
disable: u4 = 0,
|
|
276
|
-
/// The number of turns a Pokémon has been observed to be attacking.
|
|
277
|
-
attacking: u3 = 0,
|
|
278
|
-
/// The number of turns a Pokémon has been observed to be binding their opponent.
|
|
279
|
-
binding: u3 = 0,
|
|
280
|
-
/// The number of turns a Pokémon has been observed to be encored.
|
|
281
|
-
encore: u3 = 0,
|
|
282
|
-
|
|
283
|
-
_: u12 = 0,
|
|
284
|
-
|
|
285
|
-
pub const Field = std.meta.FieldEnum(Duration);
|
|
286
|
-
};
|
|
287
|
-
|
|
288
212
|
/// Tracks chance actions and their associated probability during a Generation II battle update when
|
|
289
213
|
/// `options.chance` is enabled.
|
|
290
214
|
pub fn Chance(comptime Rational: type) type {
|
|
@@ -297,37 +221,15 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
297
221
|
/// RNG events were observed during a battle `update`.
|
|
298
222
|
actions: Actions = .{},
|
|
299
223
|
|
|
300
|
-
// Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
|
|
301
|
-
// other durations which are all tied to volatiles, sleep's counter persists through
|
|
302
|
-
// switching and so we must store it here. The indices of these arrays correspond to the
|
|
303
|
-
// `order` field of a Side. This information could be stored in actions but size is a
|
|
304
|
-
// concern so this is tracked separately as actions only purports to track RNG events
|
|
305
|
-
// observed during a single `update` (not across updates).
|
|
306
|
-
sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
|
|
307
|
-
|
|
308
224
|
/// Possible error returned by operations tracking chance probability.
|
|
309
225
|
pub const Error = Rational.Error;
|
|
310
226
|
|
|
311
227
|
/// Convenience helper to clear fields which typically should be cleared between updates.
|
|
312
228
|
pub fn reset(self: *Self) void {
|
|
313
|
-
self.probability.reset();
|
|
314
|
-
self.actions.reset();
|
|
315
|
-
// TODO: self.pending = .{};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
|
|
319
229
|
if (!enabled) return;
|
|
320
230
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
var action = self.actions.get(player);
|
|
325
|
-
|
|
326
|
-
const slp = action.durations.sleep;
|
|
327
|
-
action.durations = .{};
|
|
328
|
-
|
|
329
|
-
self.sleeps[@intFromEnum(player)][out - 1] = slp;
|
|
330
|
-
action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
|
|
231
|
+
self.probability.reset();
|
|
232
|
+
self.actions = .{};
|
|
331
233
|
}
|
|
332
234
|
|
|
333
235
|
pub fn speedTie(self: *Self, p1: bool) Error!void {
|
|
@@ -517,122 +419,10 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
517
419
|
self.actions.get(player).multi_hit = n;
|
|
518
420
|
}
|
|
519
421
|
|
|
520
|
-
pub fn duration(
|
|
521
|
-
self: *Self,
|
|
522
|
-
comptime field: Duration.Field,
|
|
523
|
-
player: Player,
|
|
524
|
-
target: Player,
|
|
525
|
-
turns: u4,
|
|
526
|
-
) void {
|
|
527
|
-
if (!enabled) return;
|
|
528
|
-
|
|
529
|
-
var durations = &self.actions.get(target).durations;
|
|
530
|
-
assert(@field(durations, @tagName(field)) == 0 or
|
|
531
|
-
(field == .confusion and player == target));
|
|
532
|
-
@field(durations, @tagName(field)) = 1;
|
|
533
|
-
if (field == .confusion) durations.thrash = player == target;
|
|
534
|
-
self.actions.get(player).duration = if (options.key) 1 else turns;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
|
|
538
|
-
if (!enabled) return;
|
|
539
|
-
|
|
540
|
-
var durations = &self.actions.get(player).durations;
|
|
541
|
-
const n = durations.sleep;
|
|
542
|
-
if (turns == 0) {
|
|
543
|
-
assert(n >= 1 and n <= 6);
|
|
544
|
-
if (n != 6) try self.probability.update(1, 7 - @as(u4, n));
|
|
545
|
-
durations.sleep = 0;
|
|
546
|
-
} else {
|
|
547
|
-
assert(n >= 1 and n < 6);
|
|
548
|
-
try self.probability.update(7 - @as(u4, n) - 1, 7 - @as(u4, n));
|
|
549
|
-
durations.sleep += 1;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
|
|
422
|
+
pub fn duration(self: *Self, player: Player, turns: u4) void {
|
|
554
423
|
if (!enabled) return;
|
|
555
424
|
|
|
556
|
-
|
|
557
|
-
const n = durations.confusion;
|
|
558
|
-
const hi: u8 = if (durations.thrash) 3 else 5;
|
|
559
|
-
const p = hi + 1;
|
|
560
|
-
if (turns == 0) {
|
|
561
|
-
assert(n >= 2 and n <= hi);
|
|
562
|
-
if (n != hi) try self.probability.update(1, p - @as(u4, n));
|
|
563
|
-
durations.confusion = 0;
|
|
564
|
-
} else {
|
|
565
|
-
assert(n >= 1 and n < hi);
|
|
566
|
-
if (n > 1) try self.probability.update(p - @as(u4, n) - 1, p - @as(u4, n));
|
|
567
|
-
durations.confusion += 1;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
|
|
572
|
-
if (!enabled) return;
|
|
573
|
-
|
|
574
|
-
var durations = &self.actions.get(player).durations;
|
|
575
|
-
const n = durations.disable;
|
|
576
|
-
const hi = if (showdown) 5 else 8;
|
|
577
|
-
const p = hi + 1;
|
|
578
|
-
if (turns == 0) {
|
|
579
|
-
assert(n >= 2 and n <= hi);
|
|
580
|
-
if (n != hi) try self.probability.update(1, p - n);
|
|
581
|
-
durations.disable = 0;
|
|
582
|
-
} else {
|
|
583
|
-
assert(n >= 1 and n < hi);
|
|
584
|
-
if (n > 1) try self.probability.update(p - n - 1, p - n);
|
|
585
|
-
durations.disable += 1;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
|
|
590
|
-
if (!enabled) return;
|
|
591
|
-
|
|
592
|
-
var durations = &self.actions.get(player).durations;
|
|
593
|
-
const n = durations.attacking;
|
|
594
|
-
if (turns == 0) {
|
|
595
|
-
assert(n >= 2 and n <= 3);
|
|
596
|
-
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
597
|
-
durations.attacking = 0;
|
|
598
|
-
} else {
|
|
599
|
-
assert(n >= 1 and n < 3);
|
|
600
|
-
if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
|
|
601
|
-
durations.attacking += 1;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
|
|
606
|
-
if (!enabled) return;
|
|
607
|
-
|
|
608
|
-
var durations = &self.actions.get(player).durations;
|
|
609
|
-
const n = durations.binding;
|
|
610
|
-
if (turns == 0) {
|
|
611
|
-
assert(n >= 2 and n <= 5);
|
|
612
|
-
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
613
|
-
durations.binding = 0;
|
|
614
|
-
} else {
|
|
615
|
-
assert(n >= 1 and n < 5);
|
|
616
|
-
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
617
|
-
durations.binding += 1;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// TODO: consider sharing implementation with binding
|
|
622
|
-
pub fn encore(self: *Self, player: Player, turns: u4) Error!void {
|
|
623
|
-
if (!enabled) return;
|
|
624
|
-
|
|
625
|
-
var durations = &self.actions.get(player).durations;
|
|
626
|
-
const n = durations.encore;
|
|
627
|
-
if (turns == 0) {
|
|
628
|
-
assert(n >= 2 and n <= 5);
|
|
629
|
-
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
630
|
-
durations.encore = 0;
|
|
631
|
-
} else {
|
|
632
|
-
assert(n >= 1 and n < 5);
|
|
633
|
-
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
634
|
-
durations.encore += 1;
|
|
635
|
-
}
|
|
425
|
+
self.actions.get(player).duration = if (options.key) 2 else turns;
|
|
636
426
|
}
|
|
637
427
|
};
|
|
638
428
|
}
|
|
@@ -967,200 +757,8 @@ test "Chance.multiHit" {
|
|
|
967
757
|
test "Chance.duration" {
|
|
968
758
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
969
759
|
|
|
970
|
-
chance.duration(.
|
|
760
|
+
chance.duration(.P1, 2);
|
|
971
761
|
try expectValue(2, chance.actions.p1.duration);
|
|
972
|
-
try expectValue(1, chance.actions.p2.durations.sleep);
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
test "Chance.sleep" {
|
|
976
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
977
|
-
|
|
978
|
-
for ([_]u8{ 6, 5, 4, 3, 2, 1 }, 1..7) |d, i| {
|
|
979
|
-
chance.actions.p1.durations.sleep = @intCast(i);
|
|
980
|
-
try chance.sleep(.P1, 0);
|
|
981
|
-
try expectProbability(&chance.probability, 1, d);
|
|
982
|
-
try expectValue(0, chance.actions.p1.durations.sleep);
|
|
983
|
-
|
|
984
|
-
chance.reset();
|
|
985
|
-
|
|
986
|
-
if (i < 6) {
|
|
987
|
-
chance.actions.p1.durations.sleep = @intCast(i);
|
|
988
|
-
try chance.sleep(.P1, 1);
|
|
989
|
-
try expectProbability(&chance.probability, d - 1, d);
|
|
990
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
|
|
991
|
-
|
|
992
|
-
chance.reset();
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
test "Chance.confusion" {
|
|
998
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
999
|
-
|
|
1000
|
-
chance.actions.p2.durations.thrash = false;
|
|
1001
|
-
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
1002
|
-
if (i > 1) {
|
|
1003
|
-
chance.actions.p2.durations.confusion = @intCast(i);
|
|
1004
|
-
try chance.confusion(.P2, 0);
|
|
1005
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1006
|
-
try expectValue(0, chance.actions.p2.durations.confusion);
|
|
1007
|
-
try expectValue(false, chance.actions.p2.durations.thrash);
|
|
1008
|
-
|
|
1009
|
-
chance.reset();
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (i < 5) {
|
|
1013
|
-
chance.actions.p2.durations.confusion = @intCast(i);
|
|
1014
|
-
try chance.confusion(.P2, 1);
|
|
1015
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1016
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
|
|
1017
|
-
try expectValue(false, chance.actions.p2.durations.thrash);
|
|
1018
|
-
|
|
1019
|
-
chance.reset();
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
chance.reset();
|
|
1024
|
-
|
|
1025
|
-
chance.actions.p2.durations.thrash = true;
|
|
1026
|
-
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
1027
|
-
if (i > 1) {
|
|
1028
|
-
chance.actions.p2.durations.confusion = @intCast(i);
|
|
1029
|
-
try chance.confusion(.P2, 0);
|
|
1030
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1031
|
-
try expectValue(0, chance.actions.p2.durations.confusion);
|
|
1032
|
-
try expectValue(true, chance.actions.p2.durations.thrash);
|
|
1033
|
-
|
|
1034
|
-
chance.reset();
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
if (i < 3) {
|
|
1038
|
-
chance.actions.p2.durations.confusion = @intCast(i);
|
|
1039
|
-
try chance.confusion(.P2, 1);
|
|
1040
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1041
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
|
|
1042
|
-
try expectValue(true, chance.actions.p2.durations.thrash);
|
|
1043
|
-
|
|
1044
|
-
chance.reset();
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
test "Chance.disable" {
|
|
1050
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1051
|
-
if (showdown) {
|
|
1052
|
-
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
1053
|
-
if (i > 1) {
|
|
1054
|
-
chance.actions.p2.durations.disable = @intCast(i);
|
|
1055
|
-
try chance.disable(.P2, 0);
|
|
1056
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1057
|
-
try expectValue(0, chance.actions.p2.durations.disable);
|
|
1058
|
-
|
|
1059
|
-
chance.reset();
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
if (i < 5) {
|
|
1063
|
-
chance.actions.p2.durations.disable = @intCast(i);
|
|
1064
|
-
try chance.disable(.P2, 1);
|
|
1065
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1066
|
-
try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p2.durations.disable);
|
|
1067
|
-
|
|
1068
|
-
chance.reset();
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
} else {
|
|
1072
|
-
for ([_]u8{ 1, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
1073
|
-
if (i > 1) {
|
|
1074
|
-
chance.actions.p2.durations.disable = @intCast(i);
|
|
1075
|
-
try chance.disable(.P2, 0);
|
|
1076
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1077
|
-
try expectValue(0, chance.actions.p2.durations.disable);
|
|
1078
|
-
|
|
1079
|
-
chance.reset();
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
if (i < 8) {
|
|
1083
|
-
chance.actions.p2.durations.disable = @intCast(i);
|
|
1084
|
-
try chance.disable(.P2, 1);
|
|
1085
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1086
|
-
try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p2.durations.disable);
|
|
1087
|
-
|
|
1088
|
-
chance.reset();
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
test "Chance.attacking" {
|
|
1095
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1096
|
-
|
|
1097
|
-
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
1098
|
-
if (i > 1) {
|
|
1099
|
-
chance.actions.p2.durations.attacking = @intCast(i);
|
|
1100
|
-
try chance.attacking(.P2, 0);
|
|
1101
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1102
|
-
try expectValue(0, chance.actions.p2.durations.attacking);
|
|
1103
|
-
|
|
1104
|
-
chance.reset();
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
if (i < 3) {
|
|
1108
|
-
chance.actions.p2.durations.attacking = @intCast(i);
|
|
1109
|
-
try chance.attacking(.P2, 1);
|
|
1110
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1111
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
|
|
1112
|
-
|
|
1113
|
-
chance.reset();
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
test "Chance.binding" {
|
|
1119
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1120
|
-
|
|
1121
|
-
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
1122
|
-
if (i > 1) {
|
|
1123
|
-
chance.actions.p2.durations.binding = @intCast(i);
|
|
1124
|
-
try chance.binding(.P2, 0);
|
|
1125
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1126
|
-
try expectValue(0, chance.actions.p2.durations.binding);
|
|
1127
|
-
|
|
1128
|
-
chance.reset();
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
if (i < 5) {
|
|
1132
|
-
chance.actions.p2.durations.binding = @intCast(i);
|
|
1133
|
-
try chance.binding(.P2, 1);
|
|
1134
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1135
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.binding);
|
|
1136
|
-
|
|
1137
|
-
chance.reset();
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
test "Chance.encore" {
|
|
1143
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1144
|
-
|
|
1145
|
-
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
1146
|
-
if (i > 1) {
|
|
1147
|
-
chance.actions.p2.durations.encore = @intCast(i);
|
|
1148
|
-
try chance.encore(.P2, 0);
|
|
1149
|
-
try expectProbability(&chance.probability, 1, d);
|
|
1150
|
-
try expectValue(0, chance.actions.p2.durations.encore);
|
|
1151
|
-
|
|
1152
|
-
chance.reset();
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (i < 5) {
|
|
1156
|
-
chance.actions.p2.durations.encore = @intCast(i);
|
|
1157
|
-
try chance.encore(.P2, 1);
|
|
1158
|
-
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1159
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.encore);
|
|
1160
|
-
|
|
1161
|
-
chance.reset();
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
762
|
}
|
|
1165
763
|
|
|
1166
764
|
pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
@@ -1278,37 +876,7 @@ const Null = struct {
|
|
|
1278
876
|
_ = .{ self, player, move, n };
|
|
1279
877
|
}
|
|
1280
878
|
|
|
1281
|
-
pub fn duration(
|
|
1282
|
-
self: Null,
|
|
1283
|
-
comptime field: Duration.Field,
|
|
1284
|
-
player: Player,
|
|
1285
|
-
target: Player,
|
|
1286
|
-
turns: u4,
|
|
1287
|
-
) void {
|
|
1288
|
-
_ = .{ self, field, player, target, turns };
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
|
|
1292
|
-
_ = .{ self, player, turns };
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
|
|
1296
|
-
_ = .{ self, player, turns };
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
pub fn disable(self: Null, player: Player, turns: u4) Error!void {
|
|
1300
|
-
_ = .{ self, player, turns };
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
|
|
1304
|
-
_ = .{ self, player, turns };
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
pub fn binding(self: Null, player: Player, turns: u4) Error!void {
|
|
1308
|
-
_ = .{ self, player, turns };
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
pub fn encore(self: Null, player: Player, turns: u4) Error!void {
|
|
879
|
+
pub fn duration(self: Null, player: Player, turns: u4) void {
|
|
1312
880
|
_ = .{ self, player, turns };
|
|
1313
881
|
}
|
|
1314
882
|
};
|
package/src/lib/gen2/helpers.zig
CHANGED
|
@@ -65,17 +65,47 @@ pub const Battle = struct {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
pub fn prng(rand: *PSRNG) data.PRNG {
|
|
68
|
-
// FIXME: what is max range in gen2?
|
|
69
68
|
return .{
|
|
70
69
|
.src = .{
|
|
71
70
|
.seed = if (showdown)
|
|
72
71
|
rand.newSeed()
|
|
73
|
-
else
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
else seed: {
|
|
73
|
+
// GLITCH: The initial RNG seed in Gen II link battles is heavily constrained
|
|
74
|
+
// https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
|
|
75
|
+
var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
76
|
+
|
|
77
|
+
// Bytes in seed can only range from 0-252, not 0-255
|
|
78
|
+
const max: u8 = 253;
|
|
79
|
+
// Instructions take multiples of 4 cycles to execute
|
|
80
|
+
var timer: u16 = 4 * rand.range(u16, 0, 16384);
|
|
81
|
+
var add: u8 = rand.range(u8, 0, 256);
|
|
82
|
+
var sub: u8 = rand.range(u8, 0, 256);
|
|
83
|
+
var carry: u8 = 0;
|
|
84
|
+
var div: u8 = 0;
|
|
85
|
+
|
|
86
|
+
var i: u8 = 0;
|
|
87
|
+
while (i < seed.len) {
|
|
88
|
+
div = @intCast(timer >> 8);
|
|
89
|
+
// This is effectively @addWithOverflow but with three operands
|
|
90
|
+
const result = @as(u16, add) + @as(u16, div) + @as(u16, carry);
|
|
91
|
+
add = @intCast(result & 255);
|
|
92
|
+
carry = @intFromBool(result > 255);
|
|
93
|
+
timer +%= 44;
|
|
94
|
+
div = @intCast(timer >> 8);
|
|
95
|
+
sub -%= div +% carry;
|
|
96
|
+
|
|
97
|
+
if (sub < max) {
|
|
98
|
+
seed[i] = sub;
|
|
99
|
+
i += 1;
|
|
100
|
+
timer +%= 152;
|
|
101
|
+
carry = 1;
|
|
102
|
+
} else {
|
|
103
|
+
timer +%= 132;
|
|
104
|
+
carry = 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
break :seed seed;
|
|
108
|
+
},
|
|
79
109
|
},
|
|
80
110
|
};
|
|
81
111
|
}
|