@pkmn/engine 0.1.0-dev.8b192003 → 0.1.0-dev.8c935a85
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/README.md +9 -6
- package/build/pkg/addon/browser.d.ts +3 -0
- package/build/pkg/addon/browser.js +47 -0
- package/build/pkg/addon/browser.js.map +1 -0
- package/build/pkg/addon/node.d.ts +3 -0
- package/build/pkg/addon/node.js +110 -0
- package/build/pkg/addon/node.js.map +1 -0
- 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 -3
- package/build/pkg/addon.js +34 -65
- 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/data.d.ts +23 -1
- package/build/pkg/data.js.map +1 -1
- 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 +40 -90
- package/build/pkg/index.js +23 -70
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +20 -1
- package/build/pkg/protocol.js +105 -103
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +52 -76
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +152 -0
- package/build/tools/display/debug.jsx.map +1 -0
- package/build/tools/display/index.js +152 -0
- package/build/tools/display/index.js.map +1 -0
- package/build/tools/display/ui.jsx +359 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +33 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +330 -85
- package/package.json +54 -135
- package/src/bin/install-pkmn-engine +10 -7
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/bindings/c.zig +255 -0
- package/src/lib/bindings/node.zig +121 -0
- package/src/lib/bindings/wasm.zig +59 -0
- package/src/lib/common/array.zig +77 -0
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +39 -8
- package/src/lib/common/js.zig +4 -2
- package/src/lib/common/optional.zig +13 -9
- package/src/lib/common/options.zig +4 -0
- package/src/lib/common/protocol.zig +45 -44
- package/src/lib/common/rational.zig +256 -181
- package/src/lib/common/rng.zig +5 -7
- package/src/lib/common/util.zig +60 -1
- package/src/lib/gen1/calc.zig +314 -164
- package/src/lib/gen1/chance.zig +477 -119
- 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 +1 -2
- package/src/lib/gen1/data.zig +35 -38
- package/src/lib/gen1/helpers.zig +17 -27
- package/src/lib/gen1/mechanics.zig +249 -156
- package/src/lib/gen2/calc.zig +37 -27
- package/src/lib/gen2/chance.zig +87 -67
- 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 +1 -2
- package/src/lib/gen2/data.zig +26 -29
- package/src/lib/gen2/generated.zig +18 -23
- package/src/lib/gen2/helpers.zig +9 -18
- package/src/lib/gen2/mechanics.zig +81 -81
- package/src/lib/node.zig +3 -116
- package/src/lib/pkmn.zig +14 -7
- package/src/lib/wasm.zig +13 -9
- package/src/tools/display/dom.ts +93 -0
- package/src/tools/display/ui.css +354 -0
- package/build/test/display.js +0 -400
- package/build/test/display.js.map +0 -1
- package/src/test/display/pkmn.html.tmpl +0 -368
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -1,44 +1,43 @@
|
|
|
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 Confusion = chance.Confusion;
|
|
20
|
+
const Duration = chance.Duration;
|
|
21
|
+
const Durations = chance.Durations;
|
|
22
|
+
const enabled = pkmn.options.calc;
|
|
18
23
|
const expect = std.testing.expect;
|
|
19
24
|
const expectEqual = std.testing.expectEqual;
|
|
20
25
|
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const Player = common.Player;
|
|
25
|
-
const Result = common.Result;
|
|
26
|
-
const Choice = common.Choice;
|
|
27
|
-
|
|
26
|
+
const isPointerTo = util.isPointerTo;
|
|
27
|
+
const Observation = chance.Observation;
|
|
28
28
|
const Optional = optional.Optional;
|
|
29
|
-
|
|
30
|
-
const Rational = rational.Rational;
|
|
31
|
-
|
|
29
|
+
const Player = common.Player;
|
|
32
30
|
const PointerType = util.PointerType;
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const Chance = chance.Chance;
|
|
37
|
-
const Durations = chance.Durations;
|
|
31
|
+
const Rational = rational.Rational;
|
|
32
|
+
const Result = common.Result;
|
|
33
|
+
const Sleeps = chance.Sleeps;
|
|
38
34
|
|
|
39
35
|
const tty = true; // DEBUG
|
|
40
36
|
const summary = false; // DEBUG
|
|
41
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
|
+
|
|
42
41
|
/// Information relevant to damage calculation that occured during a Generation I battle `update`.
|
|
43
42
|
pub const Summaries = extern struct {
|
|
44
43
|
/// Relevant information for Player 1.
|
|
@@ -52,7 +51,7 @@ pub const Summaries = extern struct {
|
|
|
52
51
|
|
|
53
52
|
/// Returns the `Summary` for the given `player`.
|
|
54
53
|
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
|
|
55
|
-
assert(
|
|
54
|
+
assert(isPointerTo(self, Summaries));
|
|
56
55
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
57
56
|
}
|
|
58
57
|
};
|
|
@@ -88,21 +87,11 @@ pub const Summary = extern struct {
|
|
|
88
87
|
}
|
|
89
88
|
};
|
|
90
89
|
|
|
91
|
-
/// TODO
|
|
92
|
-
pub const Overrides = extern struct {
|
|
93
|
-
/// TODO
|
|
94
|
-
actions: Actions = .{},
|
|
95
|
-
|
|
96
|
-
comptime {
|
|
97
|
-
assert(@sizeOf(Overrides) == 16);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
90
|
/// Allows for forcing the value of specific RNG events during a Generation I battle `update` via
|
|
102
91
|
/// `overrides` and tracks `summaries` of information relevant to damage calculation.
|
|
103
92
|
pub const Calc = struct {
|
|
104
93
|
/// Overrides the normal behavior of the RNG during an `update` to force specific outcomes.
|
|
105
|
-
overrides:
|
|
94
|
+
overrides: Actions = .{},
|
|
106
95
|
/// Information relevant to damage calculation.
|
|
107
96
|
summaries: Summaries = .{},
|
|
108
97
|
|
|
@@ -113,16 +102,22 @@ pub const Calc = struct {
|
|
|
113
102
|
) ?std.meta.FieldType(Action, field) {
|
|
114
103
|
if (!enabled) return null;
|
|
115
104
|
|
|
116
|
-
const overrides
|
|
117
|
-
if (player == .P1) self.overrides.actions.p1 else self.overrides.actions.p2;
|
|
118
|
-
const val = @field(overrides, @tagName(field));
|
|
105
|
+
const val = @field(self.overrides.get(player), @tagName(field));
|
|
119
106
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
Enum => val != .None,
|
|
108
|
+
Int => val != 0,
|
|
122
109
|
else => unreachable,
|
|
123
110
|
}) val else null;
|
|
124
111
|
}
|
|
125
112
|
|
|
113
|
+
pub fn confusion(self: *Calc, player: Player) void {
|
|
114
|
+
if (!enabled) return;
|
|
115
|
+
|
|
116
|
+
if (self.overrides.get(player).confusion == .started) {
|
|
117
|
+
self.overrides.get(player).confusion = .None;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
126
121
|
pub fn base(self: *Calc, player: Player, val: u16) void {
|
|
127
122
|
if (!enabled) return;
|
|
128
123
|
|
|
@@ -156,6 +151,10 @@ const Null = struct {
|
|
|
156
151
|
return null;
|
|
157
152
|
}
|
|
158
153
|
|
|
154
|
+
pub fn confusion(self: Null, player: Player) void {
|
|
155
|
+
_ = .{ self, player };
|
|
156
|
+
}
|
|
157
|
+
|
|
159
158
|
pub fn base(self: Null, player: Player, val: u16) void {
|
|
160
159
|
_ = .{ self, player, val };
|
|
161
160
|
}
|
|
@@ -183,7 +182,7 @@ pub const Options = struct {
|
|
|
183
182
|
metronome: bool = false,
|
|
184
183
|
};
|
|
185
184
|
|
|
186
|
-
pub const MAX_FRONTIER =
|
|
185
|
+
pub const MAX_FRONTIER = 81;
|
|
187
186
|
|
|
188
187
|
pub fn transitions(
|
|
189
188
|
battle: anytype,
|
|
@@ -202,24 +201,22 @@ pub fn transitions(
|
|
|
202
201
|
var frontier = std.ArrayList(Actions).init(allocator);
|
|
203
202
|
defer frontier.deinit();
|
|
204
203
|
|
|
204
|
+
const d = options.durations;
|
|
205
|
+
|
|
205
206
|
var opts = pkmn.battle.options(
|
|
206
207
|
protocol.NULL,
|
|
207
|
-
Chance(Rational(u128)){ .probability = .{} },
|
|
208
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = d },
|
|
208
209
|
Calc{},
|
|
209
210
|
);
|
|
210
211
|
|
|
211
212
|
var b = battle;
|
|
212
213
|
_ = try b.update(c1, c2, &opts);
|
|
213
|
-
|
|
214
|
-
var d = options.durations;
|
|
215
|
-
try d.update(&battle, &opts.chance.probability, opts.chance.actions);
|
|
216
|
-
|
|
217
214
|
stats.updates += 1;
|
|
218
215
|
|
|
219
216
|
const p1 = b.side(.P1);
|
|
220
217
|
const p2 = b.side(.P2);
|
|
221
218
|
|
|
222
|
-
var p: Rational(
|
|
219
|
+
var p: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
223
220
|
try frontier.append(opts.chance.actions);
|
|
224
221
|
|
|
225
222
|
// zig fmt: off
|
|
@@ -231,9 +228,12 @@ pub fn transitions(
|
|
|
231
228
|
var i: usize = 0;
|
|
232
229
|
assert(frontier.items.len == 1);
|
|
233
230
|
while (i < frontier.items.len) : (i += 1) {
|
|
234
|
-
const
|
|
231
|
+
const f = frontier.items[i];
|
|
232
|
+
const saved = stats.saved;
|
|
233
|
+
|
|
234
|
+
var r: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
235
235
|
|
|
236
|
-
try debug(writer,
|
|
236
|
+
try debug(writer, f, .{
|
|
237
237
|
.shape = true,
|
|
238
238
|
.color = i,
|
|
239
239
|
.bold = true,
|
|
@@ -241,33 +241,44 @@ pub fn transitions(
|
|
|
241
241
|
.indent = false,
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
-
var a: Actions = .{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for (Rolls.
|
|
250
|
-
for (Rolls.
|
|
251
|
-
for (Rolls.
|
|
252
|
-
for (Rolls.
|
|
253
|
-
for (Rolls.
|
|
254
|
-
for (Rolls.
|
|
255
|
-
for (Rolls.
|
|
256
|
-
for (Rolls.
|
|
257
|
-
for (Rolls.
|
|
258
|
-
for (Rolls.
|
|
259
|
-
for (Rolls.
|
|
260
|
-
for (Rolls.
|
|
261
|
-
for (Rolls.
|
|
262
|
-
for (Rolls.
|
|
263
|
-
for (Rolls.
|
|
264
|
-
for (Rolls.
|
|
265
|
-
|
|
266
|
-
|
|
244
|
+
var a: Actions = .{
|
|
245
|
+
.p1 = .{ .metronome = p1_move, .pp = f.p1.pp, .duration = f.p1.duration },
|
|
246
|
+
.p2 = .{ .metronome = p2_move, .pp = f.p2.pp, .duration = f.p2.duration },
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
250
|
+
for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
|
|
251
|
+
for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
|
|
252
|
+
for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
|
|
253
|
+
for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
|
|
254
|
+
for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
|
|
255
|
+
for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
|
|
256
|
+
for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
257
|
+
for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
258
|
+
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
259
|
+
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
260
|
+
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
261
|
+
for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
262
|
+
for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
|
|
263
|
+
for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
|
|
264
|
+
for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
265
|
+
for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
266
|
+
for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
267
|
+
for (Rolls.psywave(f.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
|
|
268
|
+
for (Rolls.moveSlot(f.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
269
|
+
for (Rolls.moveSlot(f.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
270
|
+
for (Rolls.multiHit(f.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
271
|
+
for (Rolls.multiHit(f.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
272
|
+
for (Rolls.secondaryChance(f.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
273
|
+
for (Rolls.secondaryChance(f.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
274
|
+
for (Rolls.criticalHit(f.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
275
|
+
for (Rolls.criticalHit(f.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
|
|
276
|
+
|
|
277
|
+
var p1_dmg = Rolls.damage(f.p1, p1_hit);
|
|
267
278
|
while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
|
|
268
279
|
a.p1.damage = @intCast(p1_dmg.min);
|
|
269
280
|
|
|
270
|
-
var p2_dmg = Rolls.damage(
|
|
281
|
+
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
271
282
|
|
|
272
283
|
const p1_min: u9 = p1_dmg.min;
|
|
273
284
|
const p2_min: u9 = p2_dmg.min;
|
|
@@ -275,29 +286,26 @@ pub fn transitions(
|
|
|
275
286
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
276
287
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
277
288
|
|
|
278
|
-
opts.calc.overrides
|
|
289
|
+
opts.calc.overrides = a;
|
|
279
290
|
opts.calc.summaries = .{};
|
|
280
|
-
opts.chance = .{ .probability = .{} };
|
|
291
|
+
opts.chance = .{ .probability = .{}, .durations = d };
|
|
281
292
|
const q = &opts.chance.probability;
|
|
282
293
|
|
|
283
294
|
b = battle;
|
|
284
295
|
_ = try b.update(c1, c2, &opts);
|
|
285
296
|
|
|
286
|
-
d = options.durations;
|
|
287
|
-
try d.update(&b, &q, opts.chance.actions);
|
|
288
|
-
|
|
289
297
|
stats.updates += 1;
|
|
290
298
|
|
|
291
299
|
const summaries = &opts.calc.summaries;
|
|
292
300
|
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
293
301
|
p1_dmg.min
|
|
294
302
|
else
|
|
295
|
-
|
|
303
|
+
Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
296
304
|
const p2_max: u9 =
|
|
297
|
-
|
|
305
|
+
Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
298
306
|
|
|
299
|
-
if (opts.chance.actions.matches(
|
|
300
|
-
if (!opts.chance.actions.eql(a)) {
|
|
307
|
+
if (opts.chance.actions.matches(f)) {
|
|
308
|
+
if (!opts.chance.actions.relax().eql(a)) {
|
|
301
309
|
if (!summary) {
|
|
302
310
|
try debug(writer, opts.chance.actions, .{
|
|
303
311
|
.p1_max = p1_max,
|
|
@@ -331,10 +339,15 @@ pub fn transitions(
|
|
|
331
339
|
}
|
|
332
340
|
}
|
|
333
341
|
}
|
|
334
|
-
|
|
335
342
|
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
336
343
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
344
|
+
|
|
345
|
+
q.reduce();
|
|
337
346
|
try p.add(q);
|
|
347
|
+
p.reduce();
|
|
348
|
+
try r.add(q);
|
|
349
|
+
r.reduce();
|
|
350
|
+
|
|
338
351
|
stats.saved += 1;
|
|
339
352
|
|
|
340
353
|
if (p.q < p.p) {
|
|
@@ -342,7 +355,7 @@ pub fn transitions(
|
|
|
342
355
|
return error.TestUnexpectedResult;
|
|
343
356
|
}
|
|
344
357
|
} else {
|
|
345
|
-
if (!
|
|
358
|
+
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
346
359
|
try frontier.append(opts.chance.actions);
|
|
347
360
|
|
|
348
361
|
try debug(writer, opts.chance.actions, .{
|
|
@@ -372,15 +385,18 @@ pub fn transitions(
|
|
|
372
385
|
p2_dmg.min = p2_max;
|
|
373
386
|
}
|
|
374
387
|
|
|
375
|
-
}}}}}}}}}}}}}}}}}}}}
|
|
388
|
+
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
376
389
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
390
|
+
assert(stats.saved > saved);
|
|
391
|
+
// TODO: ziglang/zig#22667
|
|
392
|
+
// const old = @hasDecl(std.fmt, "formatFloatDecimal");
|
|
393
|
+
// if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
|
|
394
|
+
// try writer.print(
|
|
395
|
+
// " {} ({d:.2}%)\n = ──────────\n {} ({d:.2}%)\n\n",
|
|
396
|
+
// .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
|
|
397
|
+
// p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
398
|
+
// );
|
|
399
|
+
// }
|
|
384
400
|
|
|
385
401
|
}
|
|
386
402
|
|
|
@@ -404,7 +420,6 @@ pub fn transitions(
|
|
|
404
420
|
|
|
405
421
|
pub fn update(
|
|
406
422
|
battle: anytype,
|
|
407
|
-
durations: *Durations,
|
|
408
423
|
c1: Choice,
|
|
409
424
|
c2: Choice,
|
|
410
425
|
options: anytype,
|
|
@@ -412,40 +427,38 @@ pub fn update(
|
|
|
412
427
|
writer: anytype,
|
|
413
428
|
transition: bool,
|
|
414
429
|
) !Result {
|
|
430
|
+
const durations = options.chance.durations;
|
|
415
431
|
options.chance.reset();
|
|
416
432
|
|
|
417
|
-
if (!pkmn.options.chance or !pkmn.options.calc
|
|
418
|
-
return battle.update(c1, c2, options);
|
|
419
|
-
}
|
|
433
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return battle.update(c1, c2, options);
|
|
420
434
|
|
|
421
|
-
|
|
422
|
-
const saved = durations.*;
|
|
423
|
-
const actions = options.chance.actions;
|
|
435
|
+
const original = battle.*;
|
|
424
436
|
|
|
425
437
|
// Perfom the actual update
|
|
426
438
|
const result = battle.update(c1, c2, options);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
430
|
-
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
431
|
-
var stream: protocol.ByteStream = .{ .buffer = &buf };
|
|
432
|
-
const n = try helpers.diff(©, battle, stream.writer());
|
|
439
|
+
var overrides = options.chance.actions;
|
|
440
|
+
const updated = options.chance.durations;
|
|
433
441
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
try expectEqual(copy, patched);
|
|
442
|
+
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
443
|
+
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
444
|
+
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
445
|
+
var stream: protocol.ByteStream = .{ .buffer = &buf };
|
|
446
|
+
const n = try helpers.diff(&original, battle, stream.writer());
|
|
440
447
|
|
|
441
|
-
|
|
448
|
+
// Applying the diff to the battle should take us back to the original copy
|
|
449
|
+
// of the battle (ignoring the RNG).
|
|
450
|
+
var patched = battle.*;
|
|
451
|
+
patched.rng = original.rng;
|
|
452
|
+
helpers.patch(&patched, buf[0..n]);
|
|
453
|
+
try expectEqual(original, patched);
|
|
454
|
+
}
|
|
442
455
|
|
|
443
456
|
if (transition) {
|
|
444
457
|
// Ensure we can generate all transitions from the same original state
|
|
445
458
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
446
459
|
// the transitions function relies on RNG for discovery of states)
|
|
447
|
-
if (try transitions(unfix(
|
|
448
|
-
.durations =
|
|
460
|
+
if (try transitions(unfix(original), c1, c2, allocator, writer, .{
|
|
461
|
+
.durations = durations,
|
|
449
462
|
.cap = true,
|
|
450
463
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
451
464
|
}
|
|
@@ -454,21 +467,50 @@ pub fn update(
|
|
|
454
467
|
// same as we observed.
|
|
455
468
|
var override = pkmn.battle.options(
|
|
456
469
|
protocol.NULL,
|
|
457
|
-
Chance(Rational(u128)){ .probability = .{} },
|
|
458
|
-
Calc{ .overrides =
|
|
470
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
471
|
+
Calc{ .overrides = overrides },
|
|
459
472
|
);
|
|
460
|
-
|
|
473
|
+
var copy = original;
|
|
474
|
+
var overridden = copy.update(c1, c2, &override);
|
|
461
475
|
try expectEqual(result, overridden);
|
|
462
|
-
try
|
|
476
|
+
try expectEqualActions(overrides, override.chance.actions);
|
|
477
|
+
try expectEqual(updated, override.chance.durations);
|
|
463
478
|
|
|
464
479
|
// The actual battle excluding its RNG field should match a copy updated with
|
|
465
480
|
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
466
481
|
copy.rng = battle.rng;
|
|
467
|
-
try expectEqual(copy
|
|
482
|
+
try expectEqual(battle.*, copy);
|
|
483
|
+
|
|
484
|
+
// Demonstrate that even with the duration rolls changed, the duration
|
|
485
|
+
// fields on their own are sufficient to achieve the same outcomes
|
|
486
|
+
// NB: The actual battle bytes will differ because the durations won't match
|
|
487
|
+
overrides.p1.duration = if (overrides.p1.duration > 0) 2 else 0;
|
|
488
|
+
overrides.p2.duration = if (overrides.p2.duration > 0) 2 else 0;
|
|
489
|
+
|
|
490
|
+
override = pkmn.battle.options(
|
|
491
|
+
protocol.NULL,
|
|
492
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
493
|
+
Calc{ .overrides = overrides },
|
|
494
|
+
);
|
|
495
|
+
copy = original;
|
|
496
|
+
overridden = copy.update(c1, c2, &override);
|
|
497
|
+
try expectEqual(result, overridden);
|
|
498
|
+
try expectEqualActions(overrides, override.chance.actions);
|
|
499
|
+
try expectEqual(updated, override.chance.durations);
|
|
468
500
|
|
|
469
501
|
return result;
|
|
470
502
|
}
|
|
471
503
|
|
|
504
|
+
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
505
|
+
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
506
|
+
error.TestExpectedEqual => {
|
|
507
|
+
std.debug.print("expected {}, found {}\n", .{ expected, actual });
|
|
508
|
+
return e;
|
|
509
|
+
},
|
|
510
|
+
else => return e,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
472
514
|
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
473
515
|
return .{
|
|
474
516
|
.sides = actual.sides,
|
|
@@ -484,15 +526,6 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
484
526
|
};
|
|
485
527
|
}
|
|
486
528
|
|
|
487
|
-
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
488
|
-
for (frontier, 0..) |f, j| {
|
|
489
|
-
// TODO: is skipping this redundant check worth it?
|
|
490
|
-
if (i == j) continue;
|
|
491
|
-
if (f.matches(actions)) return true;
|
|
492
|
-
}
|
|
493
|
-
return false;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
529
|
fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
497
530
|
const w = std.io.getStdErr().writer();
|
|
498
531
|
w.print(fmt, v) catch return;
|
|
@@ -565,8 +598,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
|
|
|
565
598
|
try writer.writeAll(output[0..len]);
|
|
566
599
|
}
|
|
567
600
|
|
|
568
|
-
const NONE = @intFromEnum(Optional(bool).None);
|
|
569
|
-
|
|
570
601
|
/// Helper functions that efficiently return valid ranges for various RNG events based on the
|
|
571
602
|
/// state of an `Action` and other events to be used to construct a "transitions" function.
|
|
572
603
|
pub const Rolls = struct {
|
|
@@ -578,6 +609,112 @@ pub const Rolls = struct {
|
|
|
578
609
|
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
579
610
|
}
|
|
580
611
|
|
|
612
|
+
const OBS_NONE = [_]Optional(Observation){.None};
|
|
613
|
+
const OBS_STARTED = [_]Optional(Observation){.started};
|
|
614
|
+
const OBS_CONTINUING = [_]Optional(Observation){.continuing};
|
|
615
|
+
const OBS_ENDED = [_]Optional(Observation){.ended};
|
|
616
|
+
const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
|
|
617
|
+
const OBS = [_]Optional(Observation){ .continuing, .ended };
|
|
618
|
+
|
|
619
|
+
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
620
|
+
/// `durations`.
|
|
621
|
+
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
|
|
622
|
+
if (action.sleep == .None) return &OBS_NONE;
|
|
623
|
+
return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
|
|
624
|
+
&OBS_ALL
|
|
625
|
+
else switch (action.sleep) {
|
|
626
|
+
.started => &OBS_STARTED,
|
|
627
|
+
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
632
|
+
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
633
|
+
pub fn disable(
|
|
634
|
+
action: Action,
|
|
635
|
+
duration: Duration,
|
|
636
|
+
parent: Optional(Observation),
|
|
637
|
+
) []const Optional(Observation) {
|
|
638
|
+
if (action.disable == .None) return &OBS_NONE;
|
|
639
|
+
return if (duration.disable == 0 and action.speed_tie != .None)
|
|
640
|
+
&OBS_ALL
|
|
641
|
+
else switch (action.disable) {
|
|
642
|
+
.started => &OBS_STARTED,
|
|
643
|
+
else => if (duration.disable >= 8)
|
|
644
|
+
&OBS_ENDED
|
|
645
|
+
else if (parent != .None and parent != .started)
|
|
646
|
+
&OBS_CONTINUING
|
|
647
|
+
else
|
|
648
|
+
&OBS,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/// FIXME
|
|
653
|
+
pub fn attacking(
|
|
654
|
+
action: Action,
|
|
655
|
+
duration: Duration,
|
|
656
|
+
parent: Optional(Observation),
|
|
657
|
+
) []const Optional(Observation) {
|
|
658
|
+
return switch (action.attacking) {
|
|
659
|
+
.None => &OBS_NONE,
|
|
660
|
+
.started => &OBS_STARTED,
|
|
661
|
+
else => if (duration.attacking >= 3)
|
|
662
|
+
&OBS_ENDED
|
|
663
|
+
else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
|
|
664
|
+
&OBS_CONTINUING
|
|
665
|
+
else
|
|
666
|
+
&OBS,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const CFZ_NONE = [_]Optional(Confusion){.None};
|
|
671
|
+
const CFZ_STARTED = [_]Optional(Confusion){.started};
|
|
672
|
+
const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
|
|
673
|
+
const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
|
|
674
|
+
const CFZ_ENDED = [_]Optional(Confusion){.ended};
|
|
675
|
+
const CFZ = [_]Optional(Confusion){ .continuing, .ended };
|
|
676
|
+
const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
|
|
677
|
+
|
|
678
|
+
/// FIXME
|
|
679
|
+
pub fn confusion(
|
|
680
|
+
action: Action,
|
|
681
|
+
duration: Duration,
|
|
682
|
+
tie: Optional(Player),
|
|
683
|
+
sibling: Optional(Observation),
|
|
684
|
+
parent: Optional(Observation),
|
|
685
|
+
) []const Optional(Confusion) {
|
|
686
|
+
return switch (action.confusion) {
|
|
687
|
+
.None => &CFZ_NONE,
|
|
688
|
+
.started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
|
|
689
|
+
&CFZ_OVERWRITTEN
|
|
690
|
+
else
|
|
691
|
+
&CFZ_STARTED,
|
|
692
|
+
else => if (duration.confusion >= 5)
|
|
693
|
+
&CFZ_ENDED
|
|
694
|
+
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
695
|
+
&CFZ_CONTINUING
|
|
696
|
+
else if (tie != .None)
|
|
697
|
+
&CFZ_TIE
|
|
698
|
+
else
|
|
699
|
+
&CFZ,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
704
|
+
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
705
|
+
pub fn binding(
|
|
706
|
+
action: Action,
|
|
707
|
+
duration: Duration,
|
|
708
|
+
parent: Optional(bool),
|
|
709
|
+
) []const Optional(Observation) {
|
|
710
|
+
if (parent == .true) return &OBS_NONE;
|
|
711
|
+
return switch (action.binding) {
|
|
712
|
+
.None => &OBS_NONE,
|
|
713
|
+
.started => &OBS_STARTED,
|
|
714
|
+
else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
581
718
|
const BOOL_NONE = [_]Optional(bool){.None};
|
|
582
719
|
const BOOLS = [_]Optional(bool){ .false, .true };
|
|
583
720
|
|
|
@@ -591,7 +728,7 @@ pub const Rolls = struct {
|
|
|
591
728
|
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
592
729
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
593
730
|
pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
594
|
-
if (parent == .false) return &BOOL_NONE;
|
|
731
|
+
if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
|
|
595
732
|
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
596
733
|
}
|
|
597
734
|
|
|
@@ -616,7 +753,7 @@ pub const Rolls = struct {
|
|
|
616
753
|
|
|
617
754
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
618
755
|
/// given the base damage in `summaries`.
|
|
619
|
-
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool)
|
|
756
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
|
|
620
757
|
if (roll == 0) return roll;
|
|
621
758
|
|
|
622
759
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -626,9 +763,11 @@ pub const Rolls = struct {
|
|
|
626
763
|
return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
|
|
627
764
|
}
|
|
628
765
|
|
|
629
|
-
/// Returns a slice with the correct range of values for confused
|
|
630
|
-
|
|
631
|
-
|
|
766
|
+
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
767
|
+
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
768
|
+
pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
|
|
769
|
+
const done = parent == .None or parent == .ended;
|
|
770
|
+
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
632
771
|
}
|
|
633
772
|
|
|
634
773
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
@@ -638,17 +777,6 @@ pub const Rolls = struct {
|
|
|
638
777
|
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
639
778
|
}
|
|
640
779
|
|
|
641
|
-
const DURATION_NONE = [_]u2{0};
|
|
642
|
-
// NB: DURATION must be at least 2 because that is the maximum of the minimums for all rolls
|
|
643
|
-
const DURATION = [_]u2{2};
|
|
644
|
-
|
|
645
|
-
/// Returns a slice with a range of values for duration given the `action` state
|
|
646
|
-
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
647
|
-
pub fn duration(action: Action, parent: Optional(bool)) []const u2 {
|
|
648
|
-
if (parent == .false) return &DURATION_NONE;
|
|
649
|
-
return if (action.duration == 0) &DURATION_NONE else &DURATION;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
780
|
const SLOT_NONE = [_]u4{0};
|
|
653
781
|
const SLOT = [_]u4{ 1, 2, 3, 4 };
|
|
654
782
|
|
|
@@ -706,6 +834,26 @@ test "Rolls.speedTie" {
|
|
|
706
834
|
try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
|
|
707
835
|
}
|
|
708
836
|
|
|
837
|
+
test "Rolls.sleep" {
|
|
838
|
+
return error.SkipZigTest; // TODO
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
test "Rolls.disable" {
|
|
842
|
+
return error.SkipZigTest; // TODO
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
test "Rolls.confusion" {
|
|
846
|
+
return error.SkipZigTest; // TODO
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
test "Rolls.atacking" {
|
|
850
|
+
return error.SkipZigTest; // TODO
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
test "Rolls.binding" {
|
|
854
|
+
return error.SkipZigTest; // TODO
|
|
855
|
+
}
|
|
856
|
+
|
|
709
857
|
test "Rolls.damage" {
|
|
710
858
|
const actions: Actions = .{ .p2 = .{ .damage = 221 } };
|
|
711
859
|
try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
|
|
@@ -715,12 +863,12 @@ test "Rolls.damage" {
|
|
|
715
863
|
|
|
716
864
|
test "Rolls.coalesce" {
|
|
717
865
|
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
718
|
-
try expectEqual(0,
|
|
719
|
-
try expectEqual(241,
|
|
720
|
-
try expectEqual(255,
|
|
866
|
+
try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
|
|
867
|
+
try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
|
|
868
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
|
|
721
869
|
summaries.p1.damage.final = 74;
|
|
722
|
-
try expectEqual(217,
|
|
723
|
-
try expectEqual(255,
|
|
870
|
+
try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
|
|
871
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
|
|
724
872
|
}
|
|
725
873
|
|
|
726
874
|
test "Rolls.hit" {
|
|
@@ -749,14 +897,23 @@ test "Rolls.criticalHit" {
|
|
|
749
897
|
&.{ .false, .true },
|
|
750
898
|
Rolls.criticalHit(actions.p1, .None),
|
|
751
899
|
);
|
|
752
|
-
try expectEqualSlices(
|
|
900
|
+
try expectEqualSlices(
|
|
901
|
+
Optional(bool),
|
|
902
|
+
if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
|
|
903
|
+
Rolls.criticalHit(actions.p1, .false),
|
|
904
|
+
);
|
|
753
905
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
|
|
754
906
|
}
|
|
755
907
|
|
|
756
908
|
test "Rolls.confused" {
|
|
757
909
|
const actions: Actions = .{ .p2 = .{ .confused = .true } };
|
|
758
|
-
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
|
|
759
|
-
try expectEqualSlices(
|
|
910
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
|
|
911
|
+
try expectEqualSlices(
|
|
912
|
+
Optional(bool),
|
|
913
|
+
&.{ .false, .true },
|
|
914
|
+
Rolls.confused(actions.p2, .continuing),
|
|
915
|
+
);
|
|
916
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
|
|
760
917
|
}
|
|
761
918
|
|
|
762
919
|
test "Rolls.paralyzed" {
|
|
@@ -767,13 +924,6 @@ test "Rolls.paralyzed" {
|
|
|
767
924
|
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p2, .true));
|
|
768
925
|
}
|
|
769
926
|
|
|
770
|
-
test "Rolls.duration" {
|
|
771
|
-
const actions: Actions = .{ .p2 = .{ .duration = 3 } };
|
|
772
|
-
try expectEqualSlices(u2, &.{0}, Rolls.duration(actions.p1, .None));
|
|
773
|
-
try expectEqualSlices(u2, &.{2}, Rolls.duration(actions.p2, .None));
|
|
774
|
-
try expectEqualSlices(u2, &.{0}, Rolls.duration(actions.p2, .false));
|
|
775
|
-
}
|
|
776
|
-
|
|
777
927
|
test "Rolls.moveSlot" {
|
|
778
928
|
const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
|
|
779
929
|
try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
|
|
@@ -804,9 +954,9 @@ test "Rolls.psywave" {
|
|
|
804
954
|
|
|
805
955
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
806
956
|
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
807
|
-
try expectEqual(150, rolls[rolls.len - 1]);
|
|
957
|
+
try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
|
|
808
958
|
side.stored().level = 81;
|
|
809
959
|
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
810
|
-
try expectEqual(121, rolls[rolls.len - 1]);
|
|
960
|
+
try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
|
|
811
961
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
812
962
|
}
|