@pkmn/engine 0.1.0-dev.15f64810 → 0.1.0-dev.1c183b1a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +28 -11
- 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 +26 -4
- 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 +29 -1
- package/build/pkg/protocol.js +105 -103
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +83 -73
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +66 -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 +360 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +157 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +255 -102
- package/package.json +59 -138
- package/src/bin/install-pkmn-engine +25 -7
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/bindings/c.zig +200 -0
- package/src/lib/bindings/node.zig +117 -0
- package/src/lib/bindings/wasm.zig +50 -0
- package/src/lib/common/array.zig +70 -0
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +33 -28
- package/src/lib/common/js.zig +3 -7
- package/src/lib/common/optional.zig +11 -17
- package/src/lib/common/options.zig +4 -0
- package/src/lib/common/protocol.zig +196 -160
- package/src/lib/common/rational.zig +338 -254
- package/src/lib/common/rng.zig +5 -7
- package/src/lib/common/util.zig +33 -1
- package/src/lib/gen1/calc.zig +651 -205
- package/src/lib/gen1/chance.zig +412 -249
- package/src/lib/gen1/data/moves.zig +2 -4
- package/src/lib/gen1/data/species.zig +1 -3
- package/src/lib/gen1/data/types.zig +3 -4
- package/src/lib/gen1/data.zig +40 -43
- package/src/lib/gen1/helpers.zig +17 -562
- package/src/lib/gen1/mechanics.zig +328 -229
- package/src/lib/gen2/calc.zig +35 -28
- package/src/lib/gen2/chance.zig +91 -510
- package/src/lib/gen2/data/items.zig +1 -3
- package/src/lib/gen2/data/moves.zig +2 -4
- package/src/lib/gen2/data/species.zig +1 -3
- package/src/lib/gen2/data/types.zig +7 -5
- package/src/lib/gen2/data.zig +33 -35
- package/src/lib/gen2/generated.zig +18 -23
- package/src/lib/gen2/helpers.zig +9 -20
- package/src/lib/gen2/mechanics.zig +105 -132
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +4 -121
- package/src/lib/pkmn.zig +103 -22
- package/src/lib/wasm.zig +9 -9
- package/src/tools/display/dom.ts +108 -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,42 +1,35 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const pkmn = @import("../pkmn.zig");
|
|
1
|
+
const DEBUG = @import("../common/debug.zig").print;
|
|
4
2
|
|
|
3
|
+
const chance = @import("chance.zig");
|
|
5
4
|
const common = @import("../common/data.zig");
|
|
6
|
-
const
|
|
5
|
+
const data = @import("data.zig");
|
|
6
|
+
const helpers = @import("helpers.zig");
|
|
7
7
|
const optional = @import("../common/optional.zig");
|
|
8
|
+
const pkmn = @import("../pkmn.zig");
|
|
8
9
|
const protocol = @import("../common/protocol.zig");
|
|
9
10
|
const rational = @import("../common/rational.zig");
|
|
11
|
+
const std = @import("std");
|
|
10
12
|
const util = @import("../common/util.zig");
|
|
11
13
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const helpers = @import("helpers.zig");
|
|
15
|
-
|
|
14
|
+
const Action = chance.Action;
|
|
15
|
+
const Actions = chance.Actions;
|
|
16
16
|
const assert = std.debug.assert;
|
|
17
|
-
|
|
17
|
+
const Chance = chance.Chance;
|
|
18
|
+
const Choice = common.Choice;
|
|
19
|
+
const Duration = chance.Duration;
|
|
20
|
+
const Durations = chance.Durations;
|
|
21
|
+
const enabled = pkmn.options.calc;
|
|
18
22
|
const expect = std.testing.expect;
|
|
19
23
|
const expectEqual = std.testing.expectEqual;
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const Player = common.Player;
|
|
24
|
-
const Result = common.Result;
|
|
25
|
-
const Choice = common.Choice;
|
|
26
|
-
|
|
24
|
+
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
25
|
+
const isPointerTo = util.isPointerTo;
|
|
26
|
+
const Observation = chance.Observation;
|
|
27
27
|
const Optional = optional.Optional;
|
|
28
|
-
|
|
29
|
-
const Rational = rational.Rational;
|
|
30
|
-
|
|
28
|
+
const Player = common.Player;
|
|
31
29
|
const PointerType = util.PointerType;
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const Chance = chance.Chance;
|
|
36
|
-
const Durations = chance.Durations;
|
|
37
|
-
const Duration = chance.Duration;
|
|
38
|
-
|
|
39
|
-
const Rolls = helpers.Rolls;
|
|
30
|
+
const Rational = rational.Rational;
|
|
31
|
+
const Result = common.Result;
|
|
32
|
+
const Sleeps = chance.Sleeps;
|
|
40
33
|
|
|
41
34
|
const tty = true; // DEBUG
|
|
42
35
|
const summary = false; // DEBUG
|
|
@@ -54,7 +47,7 @@ pub const Summaries = extern struct {
|
|
|
54
47
|
|
|
55
48
|
/// Returns the `Summary` for the given `player`.
|
|
56
49
|
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
|
|
57
|
-
assert(
|
|
50
|
+
assert(isPointerTo(self, Summaries));
|
|
58
51
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
59
52
|
}
|
|
60
53
|
};
|
|
@@ -72,8 +65,10 @@ pub const Summary = extern struct {
|
|
|
72
65
|
/// The final computed damage that gets applied to the Pokémon. May exceed the target's HP
|
|
73
66
|
// (to determine the *actual* damage done compare the target's stored HP before and after).
|
|
74
67
|
final: u16 = 0,
|
|
75
|
-
/// Whether higher damage will saturate / result in the same outcome (e.g. additional
|
|
76
|
-
/// gets ignored due to it already breaking a Substitute or causing the target
|
|
68
|
+
/// Whether higher direct damage will saturate / result in the same outcome (e.g. additional
|
|
69
|
+
/// direct damage gets ignored due to it already breaking a Substitute or causing the target
|
|
70
|
+
/// to faint). Note that this field does not get set in scenarios where the target would
|
|
71
|
+
/// only be guaranteed to faint due to some sort of subsequent recoil or residual damage.
|
|
77
72
|
capped: bool = false,
|
|
78
73
|
|
|
79
74
|
// NOTE: 15 bits padding
|
|
@@ -88,25 +83,11 @@ pub const Summary = extern struct {
|
|
|
88
83
|
}
|
|
89
84
|
};
|
|
90
85
|
|
|
91
|
-
/// TODO
|
|
92
|
-
pub const Overrides = extern struct {
|
|
93
|
-
/// TODO
|
|
94
|
-
actions: Actions = .{},
|
|
95
|
-
/// TODO
|
|
96
|
-
durations: Durations = .{},
|
|
97
|
-
|
|
98
|
-
_: u32 = 0,
|
|
99
|
-
|
|
100
|
-
comptime {
|
|
101
|
-
assert(@sizeOf(Overrides) == 24);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
86
|
/// Allows for forcing the value of specific RNG events during a Generation I battle `update` via
|
|
106
87
|
/// `overrides` and tracks `summaries` of information relevant to damage calculation.
|
|
107
88
|
pub const Calc = struct {
|
|
108
89
|
/// Overrides the normal behavior of the RNG during an `update` to force specific outcomes.
|
|
109
|
-
overrides:
|
|
90
|
+
overrides: Actions = .{},
|
|
110
91
|
/// Information relevant to damage calculation.
|
|
111
92
|
summaries: Summaries = .{},
|
|
112
93
|
|
|
@@ -114,29 +95,23 @@ pub const Calc = struct {
|
|
|
114
95
|
self: Calc,
|
|
115
96
|
player: Player,
|
|
116
97
|
comptime field: Action.Field,
|
|
117
|
-
)
|
|
98
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
118
99
|
if (!enabled) return null;
|
|
119
100
|
|
|
120
|
-
const overrides
|
|
121
|
-
if (player == .P1) self.overrides.actions.p1 else self.overrides.actions.p2;
|
|
122
|
-
const val = @field(overrides, @tagName(field));
|
|
101
|
+
const val = @field(self.overrides.get(player), @tagName(field));
|
|
123
102
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
124
|
-
|
|
125
|
-
.
|
|
103
|
+
.@"enum" => val != .None,
|
|
104
|
+
.int => val != 0,
|
|
126
105
|
else => unreachable,
|
|
127
106
|
}) val else null;
|
|
128
107
|
}
|
|
129
108
|
|
|
130
|
-
pub fn
|
|
131
|
-
if (!enabled) return
|
|
132
|
-
|
|
133
|
-
const durations = self.overrides.durations;
|
|
134
|
-
const set = @field(if (player == .P1) durations.p1 else durations.p2, @tagName(field));
|
|
135
|
-
if (set == 0) return null;
|
|
109
|
+
pub fn confusion(self: *Calc, player: Player) void {
|
|
110
|
+
if (!enabled) return;
|
|
136
111
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
112
|
+
if (self.overrides.get(player).confusion == .started) {
|
|
113
|
+
self.overrides.get(player).confusion = .None;
|
|
114
|
+
}
|
|
140
115
|
}
|
|
141
116
|
|
|
142
117
|
pub fn base(self: *Calc, player: Player, val: u16) void {
|
|
@@ -167,14 +142,13 @@ const Null = struct {
|
|
|
167
142
|
self: Null,
|
|
168
143
|
player: Player,
|
|
169
144
|
comptime field: Action.Field,
|
|
170
|
-
)
|
|
145
|
+
) ?@FieldType(Action, @tagName(field)) {
|
|
171
146
|
_ = .{ self, player };
|
|
172
147
|
return null;
|
|
173
148
|
}
|
|
174
149
|
|
|
175
|
-
pub fn
|
|
176
|
-
_ = .{ self, player
|
|
177
|
-
return null;
|
|
150
|
+
pub fn confusion(self: Null, player: Player) void {
|
|
151
|
+
_ = .{ self, player };
|
|
178
152
|
}
|
|
179
153
|
|
|
180
154
|
pub fn base(self: Null, player: Player, val: u16) void {
|
|
@@ -198,39 +172,38 @@ pub const Stats = struct {
|
|
|
198
172
|
};
|
|
199
173
|
|
|
200
174
|
pub const Options = struct {
|
|
201
|
-
|
|
175
|
+
durations: Durations,
|
|
202
176
|
seed: ?u64 = null,
|
|
203
177
|
cap: bool = false,
|
|
204
178
|
metronome: bool = false,
|
|
205
179
|
};
|
|
206
180
|
|
|
207
|
-
pub const MAX_FRONTIER =
|
|
181
|
+
pub const MAX_FRONTIER = 86;
|
|
208
182
|
|
|
209
183
|
pub fn transitions(
|
|
210
184
|
battle: anytype,
|
|
211
185
|
c1: Choice,
|
|
212
186
|
c2: Choice,
|
|
213
187
|
allocator: std.mem.Allocator,
|
|
214
|
-
|
|
188
|
+
out: *std.Io.Writer,
|
|
189
|
+
err: *std.Io.Writer,
|
|
215
190
|
options: Options,
|
|
216
191
|
) !?Stats {
|
|
217
192
|
var stats: Stats = .{};
|
|
218
193
|
|
|
219
|
-
const actions = options.actions;
|
|
220
194
|
const cap = options.cap;
|
|
221
195
|
|
|
222
|
-
var seen
|
|
196
|
+
var seen: std.AutoHashMap(Actions, void) = .init(allocator);
|
|
223
197
|
defer seen.deinit();
|
|
224
|
-
var frontier
|
|
225
|
-
defer frontier.deinit();
|
|
198
|
+
var frontier: std.array_list.Aligned(Actions, null) = .empty;
|
|
199
|
+
defer frontier.deinit(allocator);
|
|
200
|
+
|
|
201
|
+
const d = options.durations;
|
|
226
202
|
|
|
227
203
|
var opts = pkmn.battle.options(
|
|
228
204
|
protocol.NULL,
|
|
229
|
-
Chance(Rational(u128)){ .probability = .{}, .
|
|
230
|
-
Calc{
|
|
231
|
-
.p1 = actions.p1.durations,
|
|
232
|
-
.p2 = actions.p2.durations,
|
|
233
|
-
} } },
|
|
205
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = d },
|
|
206
|
+
Calc{},
|
|
234
207
|
);
|
|
235
208
|
|
|
236
209
|
var b = battle;
|
|
@@ -240,8 +213,8 @@ pub fn transitions(
|
|
|
240
213
|
const p1 = b.side(.P1);
|
|
241
214
|
const p2 = b.side(.P2);
|
|
242
215
|
|
|
243
|
-
var p: Rational(
|
|
244
|
-
try frontier.append(opts.chance.actions);
|
|
216
|
+
var p: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
217
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
245
218
|
|
|
246
219
|
// zig fmt: off
|
|
247
220
|
for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
|
|
@@ -252,139 +225,172 @@ pub fn transitions(
|
|
|
252
225
|
var i: usize = 0;
|
|
253
226
|
assert(frontier.items.len == 1);
|
|
254
227
|
while (i < frontier.items.len) : (i += 1) {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
228
|
+
const f = frontier.items[i];
|
|
229
|
+
const saved = stats.saved;
|
|
230
|
+
|
|
231
|
+
var r: Rational(u256) = .{ .p = 0, .q = 1 };
|
|
232
|
+
|
|
233
|
+
try debug(out, f, .{
|
|
234
|
+
.shape = true,
|
|
235
|
+
.color = i,
|
|
236
|
+
.bold = true,
|
|
237
|
+
.background = true,
|
|
238
|
+
.indent = false,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
var a: Actions = .{
|
|
242
|
+
.p1 = .{ .metronome = p1_move, .pp = f.p1.pp, .duration = f.p1.duration },
|
|
243
|
+
.p2 = .{ .metronome = p2_move, .pp = f.p2.pp, .duration = f.p2.duration },
|
|
259
244
|
};
|
|
260
245
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
for (Rolls.
|
|
266
|
-
for (Rolls.
|
|
267
|
-
for (Rolls.
|
|
268
|
-
for (Rolls.
|
|
269
|
-
for (Rolls.
|
|
270
|
-
for (Rolls.
|
|
271
|
-
for (Rolls.
|
|
272
|
-
for (Rolls.
|
|
273
|
-
for (Rolls.
|
|
274
|
-
for (Rolls.
|
|
275
|
-
for (Rolls.
|
|
276
|
-
for (Rolls.
|
|
277
|
-
for (Rolls.
|
|
278
|
-
for (Rolls.
|
|
279
|
-
for (Rolls.
|
|
280
|
-
for (Rolls.
|
|
281
|
-
for (Rolls.
|
|
282
|
-
for (Rolls.
|
|
283
|
-
for (Rolls.
|
|
284
|
-
for (Rolls.
|
|
285
|
-
for (Rolls.
|
|
286
|
-
for (Rolls.
|
|
287
|
-
for (Rolls.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
for (Rolls.secondaryChance(template.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
291
|
-
for (Rolls.secondaryChance(template.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
292
|
-
for (Rolls.criticalHit(template.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
293
|
-
for (Rolls.criticalHit(template.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
|
|
294
|
-
|
|
295
|
-
var p1_dmg = Rolls.damage(template.p1, p1_hit);
|
|
246
|
+
for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
247
|
+
for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
|
|
248
|
+
for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
|
|
249
|
+
for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
|
|
250
|
+
for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
|
|
251
|
+
for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
|
|
252
|
+
for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
|
|
253
|
+
for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
254
|
+
for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
255
|
+
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
256
|
+
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
257
|
+
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
258
|
+
for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
259
|
+
for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
|
|
260
|
+
for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
|
|
261
|
+
for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
262
|
+
for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
263
|
+
for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
264
|
+
for (Rolls.psywave(f.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
|
|
265
|
+
for (Rolls.moveSlot(f.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
266
|
+
for (Rolls.moveSlot(f.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
267
|
+
for (Rolls.multiHit(f.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
268
|
+
for (Rolls.multiHit(f.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
269
|
+
for (Rolls.secondaryChance(f.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
270
|
+
for (Rolls.secondaryChance(f.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
271
|
+
for (Rolls.criticalHit(f.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
272
|
+
for (Rolls.criticalHit(f.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
|
|
273
|
+
|
|
274
|
+
var p1_dmg = Rolls.damage(f.p1, p1_hit);
|
|
296
275
|
while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
|
|
297
276
|
a.p1.damage = @intCast(p1_dmg.min);
|
|
298
|
-
var p1_min: u8 = 0;
|
|
299
277
|
|
|
300
|
-
var p2_dmg = Rolls.damage(
|
|
278
|
+
var p2_dmg = Rolls.damage(f.p2, p2_hit);
|
|
279
|
+
|
|
280
|
+
const p1_min: u9 = @intCast(p1_dmg.min);
|
|
281
|
+
const p2_min: u9 = @intCast(p2_dmg.min);
|
|
282
|
+
|
|
301
283
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
302
284
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
303
285
|
|
|
304
|
-
opts.calc.overrides
|
|
305
|
-
opts.
|
|
286
|
+
opts.calc.overrides = a;
|
|
287
|
+
opts.calc.summaries = .{};
|
|
288
|
+
opts.chance = .{ .probability = .{}, .durations = d };
|
|
306
289
|
const q = &opts.chance.probability;
|
|
307
290
|
|
|
308
291
|
b = battle;
|
|
309
292
|
_ = try b.update(c1, c2, &opts);
|
|
310
|
-
stats.updates += 1;
|
|
311
|
-
|
|
312
|
-
const p1_max: u8 = @intCast(p1_dmg.min);
|
|
313
|
-
const p2_max: u8 = @intCast(p2_dmg.min);
|
|
314
|
-
_ = cap;
|
|
315
293
|
|
|
316
|
-
|
|
317
|
-
// try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), &opts.calc.summaries, cap);
|
|
318
|
-
// const p2_max =
|
|
319
|
-
// try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), &opts.calc.summaries, cap);
|
|
294
|
+
stats.updates += 1;
|
|
320
295
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
296
|
+
const summaries = &opts.calc.summaries;
|
|
297
|
+
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
298
|
+
@intCast(p1_dmg.min)
|
|
299
|
+
else
|
|
300
|
+
Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
301
|
+
const p2_max: u9 =
|
|
302
|
+
Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
303
|
+
|
|
304
|
+
if (opts.chance.actions.matches(f)) {
|
|
305
|
+
if (!opts.chance.actions.relax().eql(a)) {
|
|
306
|
+
if (!summary) {
|
|
307
|
+
try debug(out, opts.chance.actions, .{
|
|
308
|
+
.p1_max = p1_max,
|
|
309
|
+
.p2_max = p2_max,
|
|
310
|
+
.color = i,
|
|
311
|
+
.dim = true,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
324
314
|
|
|
325
|
-
|
|
315
|
+
p1_dmg.min = p1_max;
|
|
326
316
|
p2_dmg.min = p2_max;
|
|
327
317
|
continue;
|
|
328
318
|
}
|
|
329
319
|
|
|
330
|
-
if (!summary)
|
|
320
|
+
if (!summary) {
|
|
321
|
+
try debug(out, opts.chance.actions, .{
|
|
322
|
+
.p1_max = p1_max,
|
|
323
|
+
.p2_max = p2_max,
|
|
324
|
+
.color = i,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
331
327
|
|
|
332
|
-
for (
|
|
333
|
-
for (p2_dmg.min
|
|
328
|
+
for (p1_min..p1_max + 1) |p1d| {
|
|
329
|
+
for (p2_dmg.min..p2_max + 1) |p2d| {
|
|
334
330
|
var acts = opts.chance.actions;
|
|
335
331
|
acts.p1.damage = @intCast(p1d);
|
|
336
332
|
acts.p2.damage = @intCast(p2d);
|
|
337
333
|
if ((try seen.getOrPut(acts)).found_existing) {
|
|
338
|
-
err
|
|
334
|
+
die(err, "already seen {f}", .{acts}, options.seed);
|
|
339
335
|
return error.TestUnexpectedResult;
|
|
340
336
|
}
|
|
341
337
|
}
|
|
342
338
|
}
|
|
343
|
-
|
|
344
|
-
if (p1_max != p1_dmg.min) try q.update(p1_max - p1_dmg.min + 1, 1);
|
|
339
|
+
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
345
340
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
341
|
+
|
|
342
|
+
q.reduce();
|
|
346
343
|
try p.add(q);
|
|
344
|
+
p.reduce();
|
|
345
|
+
try r.add(q);
|
|
346
|
+
r.reduce();
|
|
347
|
+
|
|
347
348
|
stats.saved += 1;
|
|
348
349
|
|
|
349
350
|
if (p.q < p.p) {
|
|
350
|
-
err
|
|
351
|
+
die(err, "improper fraction {f}", .{p}, options.seed);
|
|
351
352
|
return error.TestUnexpectedResult;
|
|
352
353
|
}
|
|
353
354
|
} else {
|
|
355
|
+
if (!opts.chance.actions.matchesAny(frontier.items, i)) {
|
|
356
|
+
try frontier.append(allocator, opts.chance.actions);
|
|
354
357
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
try
|
|
358
|
+
try debug(out, opts.chance.actions, .{
|
|
359
|
+
.p1_max = p1_max,
|
|
360
|
+
.p2_max = p2_max,
|
|
361
|
+
.dim = true,
|
|
362
|
+
.newline = false,
|
|
363
|
+
});
|
|
364
|
+
try out.writeAll(" → ");
|
|
365
|
+
try debug(out, opts.chance.actions, .{
|
|
366
|
+
.shape = true,
|
|
362
367
|
.color = frontier.items.len - 1,
|
|
363
368
|
.dim = true,
|
|
364
369
|
.background = true,
|
|
365
370
|
.indent = false,
|
|
366
371
|
});
|
|
367
372
|
} else if (!summary) {
|
|
368
|
-
try debug(
|
|
373
|
+
try debug(out, opts.chance.actions, .{
|
|
374
|
+
.p1_max = p1_max,
|
|
375
|
+
.p2_max = p2_max,
|
|
376
|
+
.dim = true,
|
|
377
|
+
});
|
|
369
378
|
}
|
|
370
|
-
|
|
371
|
-
// if (opts.chance.actions.p1.damage == 0) p1_max = 255;
|
|
372
|
-
// if (opts.chance.actions.p2.damage == 0) p2_max = 255;
|
|
373
379
|
}
|
|
374
380
|
|
|
375
|
-
|
|
381
|
+
p1_dmg.min = p1_max;
|
|
376
382
|
p2_dmg.min = p2_max;
|
|
377
383
|
}
|
|
378
384
|
|
|
379
|
-
|
|
380
|
-
p1_dmg.min = p1_min;
|
|
381
|
-
|
|
382
|
-
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
385
|
+
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
383
386
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
387
|
+
assert(stats.saved > saved);
|
|
388
|
+
// TODO: LLVM - float_from_int from 'u256' without intrinsics
|
|
389
|
+
// try out.print(
|
|
390
|
+
// " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
|
|
391
|
+
// .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
|
|
392
|
+
// p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
393
|
+
// );
|
|
388
394
|
|
|
389
395
|
}
|
|
390
396
|
|
|
@@ -399,7 +405,7 @@ pub fn transitions(
|
|
|
399
405
|
|
|
400
406
|
p.reduce();
|
|
401
407
|
if (p.p != 1 or p.q != 1) {
|
|
402
|
-
err
|
|
408
|
+
die(err, "expected 1, found {f}", .{p}, options.seed);
|
|
403
409
|
return error.TestExpectedEqual;
|
|
404
410
|
}
|
|
405
411
|
|
|
@@ -412,64 +418,93 @@ pub fn update(
|
|
|
412
418
|
c2: Choice,
|
|
413
419
|
options: anytype,
|
|
414
420
|
allocator: std.mem.Allocator,
|
|
415
|
-
|
|
421
|
+
out: *std.Io.Writer,
|
|
422
|
+
err: *std.Io.Writer,
|
|
416
423
|
transition: bool,
|
|
417
424
|
) !Result {
|
|
425
|
+
const durations = options.chance.durations;
|
|
418
426
|
options.chance.reset();
|
|
419
427
|
|
|
420
|
-
if (!pkmn.options.chance or !pkmn.options.calc
|
|
421
|
-
return battle.update(c1, c2, options);
|
|
422
|
-
}
|
|
428
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return battle.update(c1, c2, options);
|
|
423
429
|
|
|
424
|
-
|
|
425
|
-
const actions = options.chance.actions;
|
|
430
|
+
const original = battle.*;
|
|
426
431
|
|
|
427
432
|
// Perfom the actual update
|
|
428
433
|
const result = battle.update(c1, c2, options);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
434
|
+
var overrides = options.chance.actions;
|
|
435
|
+
const updated = options.chance.durations;
|
|
436
|
+
|
|
437
|
+
if (@TypeOf(battle.rng) == data.PRNG) {
|
|
438
|
+
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
439
|
+
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
440
|
+
var pw: protocol.Writer = .{ .buffer = &buf };
|
|
441
|
+
const n = try helpers.diff(&original, battle, &pw);
|
|
442
|
+
|
|
443
|
+
// Applying the diff to the battle should take us back to the original copy
|
|
444
|
+
// of the battle (ignoring the RNG).
|
|
445
|
+
var patched = battle.*;
|
|
446
|
+
patched.rng = original.rng;
|
|
447
|
+
helpers.patch(&patched, buf[0..n]);
|
|
448
|
+
try expectEqual(original, patched);
|
|
449
|
+
}
|
|
443
450
|
|
|
444
451
|
if (transition) {
|
|
445
452
|
// Ensure we can generate all transitions from the same original state
|
|
446
453
|
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
447
454
|
// the transitions function relies on RNG for discovery of states)
|
|
448
|
-
if (try transitions(unfix(
|
|
449
|
-
.
|
|
455
|
+
if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
|
|
456
|
+
.durations = durations,
|
|
450
457
|
.cap = true,
|
|
451
458
|
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
// Demonstrate that we can produce the same state by forcing the RNG to behave the
|
|
455
|
-
// same as we observed
|
|
456
|
-
// mask none of the durations will be extended.
|
|
462
|
+
// same as we observed.
|
|
457
463
|
var override = pkmn.battle.options(
|
|
458
464
|
protocol.NULL,
|
|
459
|
-
|
|
460
|
-
Calc{ .overrides =
|
|
465
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
466
|
+
Calc{ .overrides = overrides },
|
|
461
467
|
);
|
|
462
|
-
|
|
468
|
+
var copy = original;
|
|
469
|
+
var overridden = copy.update(c1, c2, &override);
|
|
463
470
|
try expectEqual(result, overridden);
|
|
471
|
+
try expectEqualActions(overrides, override.chance.actions);
|
|
472
|
+
try expectEqual(updated, override.chance.durations);
|
|
464
473
|
|
|
465
474
|
// The actual battle excluding its RNG field should match a copy updated with
|
|
466
475
|
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
467
476
|
copy.rng = battle.rng;
|
|
468
|
-
try expectEqual(copy
|
|
477
|
+
try expectEqual(battle.*, copy);
|
|
478
|
+
|
|
479
|
+
// Demonstrate that even with the duration rolls changed, the duration
|
|
480
|
+
// fields on their own are sufficient to achieve the same outcomes
|
|
481
|
+
// NB: The actual battle bytes will differ because the durations won't match
|
|
482
|
+
overrides.p1.duration = if (overrides.p1.duration > 0) 2 else 0;
|
|
483
|
+
overrides.p2.duration = if (overrides.p2.duration > 0) 2 else 0;
|
|
484
|
+
|
|
485
|
+
override = pkmn.battle.options(
|
|
486
|
+
protocol.NULL,
|
|
487
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
488
|
+
Calc{ .overrides = overrides },
|
|
489
|
+
);
|
|
490
|
+
copy = original;
|
|
491
|
+
overridden = copy.update(c1, c2, &override);
|
|
492
|
+
try expectEqual(result, overridden);
|
|
493
|
+
try expectEqualActions(overrides, override.chance.actions);
|
|
494
|
+
try expectEqual(updated, override.chance.durations);
|
|
469
495
|
|
|
470
496
|
return result;
|
|
471
497
|
}
|
|
472
498
|
|
|
499
|
+
fn expectEqualActions(expected: Actions, actual: Actions) !void {
|
|
500
|
+
return expectEqual(expected, actual) catch |e| switch (e) {
|
|
501
|
+
error.TestExpectedEqual => {
|
|
502
|
+
std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
|
|
503
|
+
return e;
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
473
508
|
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
474
509
|
return .{
|
|
475
510
|
.sides = actual.sides,
|
|
@@ -485,23 +520,16 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
485
520
|
};
|
|
486
521
|
}
|
|
487
522
|
|
|
488
|
-
fn
|
|
489
|
-
for (frontier, 0..) |f, j| {
|
|
490
|
-
// TODO: is skipping this redundant check worth it?
|
|
491
|
-
if (i == j) continue;
|
|
492
|
-
if (f.matches(actions)) return true;
|
|
493
|
-
}
|
|
494
|
-
return false;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
498
|
-
const w = std.io.getStdErr().writer();
|
|
523
|
+
fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
499
524
|
w.print(fmt, v) catch return;
|
|
500
|
-
if (seed) |s| return w.print("{}\n", .{s}) catch return;
|
|
525
|
+
if (seed) |s| return w.print("{d}\n", .{s}) catch return;
|
|
501
526
|
return w.writeByte('\n') catch return;
|
|
502
527
|
}
|
|
503
528
|
|
|
504
529
|
const Style = struct {
|
|
530
|
+
shape: bool = false,
|
|
531
|
+
p1_max: u9 = 0,
|
|
532
|
+
p2_max: u9 = 0,
|
|
505
533
|
color: ?usize = null,
|
|
506
534
|
bold: bool = false,
|
|
507
535
|
background: bool = false,
|
|
@@ -510,7 +538,7 @@ const Style = struct {
|
|
|
510
538
|
indent: bool = true,
|
|
511
539
|
};
|
|
512
540
|
|
|
513
|
-
fn debug(writer:
|
|
541
|
+
fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
|
|
514
542
|
if (style.indent) try writer.writeAll(" ");
|
|
515
543
|
if (tty) {
|
|
516
544
|
const mod: usize = if (style.dim) 2 else 1;
|
|
@@ -519,11 +547,429 @@ fn debug(writer: anytype, actions: Actions, shape: bool, style: Style) !void {
|
|
|
519
547
|
|
|
520
548
|
if (style.dim or style.bold) try writer.print("\x1b[{d}m", .{mod});
|
|
521
549
|
try writer.print("\x1b[{d}{d}m", .{ background, color });
|
|
522
|
-
|
|
550
|
+
const p1 = if (style.p1_max != 0 and style.p1_max != actions.p1.damage) style.p1_max else 0;
|
|
551
|
+
const p2 = if (style.p2_max != 0 and style.p2_max != actions.p2.damage) style.p2_max else 0;
|
|
552
|
+
if (p1 != 0 or p2 != 0) {
|
|
553
|
+
assert(!style.shape);
|
|
554
|
+
try format(writer, actions, p1, p2);
|
|
555
|
+
} else {
|
|
556
|
+
try actions.fmt(writer, style.shape);
|
|
557
|
+
}
|
|
523
558
|
try writer.writeAll("\x1b[0m");
|
|
524
559
|
} else {
|
|
525
560
|
if (style.dim) try writer.writeAll(" ");
|
|
526
|
-
try actions.fmt(writer, shape);
|
|
561
|
+
try actions.fmt(writer, style.shape);
|
|
527
562
|
}
|
|
528
563
|
if (style.newline) try writer.writeByte('\n');
|
|
529
564
|
}
|
|
565
|
+
|
|
566
|
+
fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
|
|
567
|
+
var input: [1024]u8 = undefined;
|
|
568
|
+
var output: [1024]u8 = undefined;
|
|
569
|
+
|
|
570
|
+
var w = std.Io.Writer.fixed(&input);
|
|
571
|
+
try actions.fmt(&w, false);
|
|
572
|
+
var len = w.buffered().len;
|
|
573
|
+
@memcpy(output[0..len], input[0..len]);
|
|
574
|
+
|
|
575
|
+
var find: [16]u8 = undefined;
|
|
576
|
+
var repl: [22]u8 = undefined;
|
|
577
|
+
if (p1 != 0 and actions.p1.damage != 0) {
|
|
578
|
+
const n = try std.fmt.bufPrint(&find, "P1 = (damage:{d}", .{actions.p1.damage});
|
|
579
|
+
const r = try std.fmt.bufPrint(&repl, "P1 = (damage:{d}…{d}", .{ actions.p1.damage, p1 });
|
|
580
|
+
assert(std.mem.replace(u8, input[0..len], n, r, output[0 .. len + 6]) == 1);
|
|
581
|
+
@memcpy(input[0 .. len + 6], output[0 .. len + 6]);
|
|
582
|
+
len += 6;
|
|
583
|
+
}
|
|
584
|
+
if (p2 != 0 and actions.p2.damage != 0) {
|
|
585
|
+
const n = try std.fmt.bufPrint(&find, "P2 = (damage:{d}", .{actions.p2.damage});
|
|
586
|
+
const r = try std.fmt.bufPrint(&repl, "P2 = (damage:{d}…{d}", .{ actions.p2.damage, p2 });
|
|
587
|
+
assert(std.mem.replace(u8, input[0..len], n, r, output[0 .. len + 6]) == 1);
|
|
588
|
+
len += 6;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
try writer.writeAll(output[0..len]);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/// Helper functions that efficiently return valid ranges for various RNG events based on the
|
|
595
|
+
/// state of an `Action` and other events to be used to construct a "transitions" function.
|
|
596
|
+
pub const Rolls = struct {
|
|
597
|
+
const PLAYER_NONE = [_]Optional(Player){.None};
|
|
598
|
+
const PLAYERS = [_]Optional(Player){ .P1, .P2 };
|
|
599
|
+
|
|
600
|
+
/// Returns a slice with the correct range of values for speed ties given the `action` state.
|
|
601
|
+
pub fn speedTie(action: Action) []const Optional(Player) {
|
|
602
|
+
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const SLP_NONE = [_]Optional(Observation(.sleep)){.None};
|
|
606
|
+
const SLP_STARTED = [_]Optional(Observation(.sleep)){.started};
|
|
607
|
+
const SLP_ENDED = [_]Optional(Observation(.sleep)){.ended};
|
|
608
|
+
const SLP_ALL = [_]Optional(Observation(.sleep)){ .started, .continuing, .ended };
|
|
609
|
+
const SLP = [_]Optional(Observation(.sleep)){ .continuing, .ended };
|
|
610
|
+
|
|
611
|
+
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
612
|
+
/// `durations`.
|
|
613
|
+
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation(.sleep)) {
|
|
614
|
+
if (action.sleep == .None) return &SLP_NONE;
|
|
615
|
+
return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
|
|
616
|
+
&SLP_ALL
|
|
617
|
+
else switch (action.sleep) {
|
|
618
|
+
.started => &SLP_STARTED,
|
|
619
|
+
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &SLP_ENDED else &SLP,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const DIS_NONE = [_]Optional(Observation(.disable)){.None};
|
|
624
|
+
const DIS_STARTED = [_]Optional(Observation(.disable)){.started};
|
|
625
|
+
const DIS_CONTINUING = [_]Optional(Observation(.disable)){.continuing};
|
|
626
|
+
const DIS_ENDED = [_]Optional(Observation(.disable)){.ended};
|
|
627
|
+
const DIS_ALL = [_]Optional(Observation(.disable)){ .started, .continuing, .ended };
|
|
628
|
+
const DIS = [_]Optional(Observation(.disable)){ .continuing, .ended };
|
|
629
|
+
|
|
630
|
+
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
631
|
+
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
632
|
+
pub fn disable(
|
|
633
|
+
action: Action,
|
|
634
|
+
duration: Duration,
|
|
635
|
+
parent: Optional(Observation(.sleep)),
|
|
636
|
+
) []const Optional(Observation(.disable)) {
|
|
637
|
+
if (action.disable == .None) return &DIS_NONE;
|
|
638
|
+
return if (duration.disable == 0 and action.speed_tie != .None)
|
|
639
|
+
&DIS_ALL
|
|
640
|
+
else switch (action.disable) {
|
|
641
|
+
.started => &DIS_STARTED,
|
|
642
|
+
else => if (duration.disable >= 8)
|
|
643
|
+
&DIS_ENDED
|
|
644
|
+
else if (parent != .None and parent != .started)
|
|
645
|
+
&DIS_CONTINUING
|
|
646
|
+
else
|
|
647
|
+
&DIS,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const ATK_NONE = [_]Optional(Observation(.attacking)){.None};
|
|
652
|
+
const ATK_STARTED = [_]Optional(Observation(.attacking)){.started};
|
|
653
|
+
const ATK_CONTINUING = [_]Optional(Observation(.attacking)){.continuing};
|
|
654
|
+
const ATK_ENDED = [_]Optional(Observation(.attacking)){.ended};
|
|
655
|
+
const ATK = [_]Optional(Observation(.attacking)){ .continuing, .ended };
|
|
656
|
+
|
|
657
|
+
/// FIXME document
|
|
658
|
+
pub fn attacking(
|
|
659
|
+
action: Action,
|
|
660
|
+
duration: Duration,
|
|
661
|
+
parent: Optional(Observation(.sleep)),
|
|
662
|
+
) []const Optional(Observation(.attacking)) {
|
|
663
|
+
return switch (action.attacking) {
|
|
664
|
+
.None => &ATK_NONE,
|
|
665
|
+
.started => &ATK_STARTED,
|
|
666
|
+
else => if (duration.attacking >= 3)
|
|
667
|
+
&ATK_ENDED
|
|
668
|
+
else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
|
|
669
|
+
&ATK_CONTINUING
|
|
670
|
+
else
|
|
671
|
+
&ATK,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const CFZ_NONE = [_]Optional(Observation(.confusion)){.None};
|
|
676
|
+
const CFZ_STARTED = [_]Optional(Observation(.confusion)){.started};
|
|
677
|
+
const CFZ_OVERWRITTEN = [_]Optional(Observation(.confusion)){ .started, .overwritten };
|
|
678
|
+
const CFZ_CONTINUING = [_]Optional(Observation(.confusion)){.continuing};
|
|
679
|
+
const CFZ_ENDED = [_]Optional(Observation(.confusion)){.ended};
|
|
680
|
+
const CFZ = [_]Optional(Observation(.confusion)){ .continuing, .ended };
|
|
681
|
+
const CFZ_TIE = [_]Optional(Observation(.confusion)){ .started, .continuing, .ended };
|
|
682
|
+
|
|
683
|
+
/// FIXME document
|
|
684
|
+
pub fn confusion(
|
|
685
|
+
action: Action,
|
|
686
|
+
duration: Duration,
|
|
687
|
+
tie: Optional(Player),
|
|
688
|
+
sibling: Optional(Observation(.attacking)),
|
|
689
|
+
parent: Optional(Observation(.sleep)),
|
|
690
|
+
) []const Optional(Observation(.confusion)) {
|
|
691
|
+
return switch (action.confusion) {
|
|
692
|
+
.None => &CFZ_NONE,
|
|
693
|
+
.started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
|
|
694
|
+
&CFZ_OVERWRITTEN
|
|
695
|
+
else
|
|
696
|
+
&CFZ_STARTED,
|
|
697
|
+
else => if (duration.confusion >= 5)
|
|
698
|
+
&CFZ_ENDED
|
|
699
|
+
else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
|
|
700
|
+
&CFZ_CONTINUING
|
|
701
|
+
else if (tie != .None)
|
|
702
|
+
&CFZ_TIE
|
|
703
|
+
else
|
|
704
|
+
&CFZ,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const BND_NONE = [_]Optional(Observation(.binding)){.None};
|
|
709
|
+
const BND_STARTED = [_]Optional(Observation(.binding)){.started};
|
|
710
|
+
const BND_ENDED = [_]Optional(Observation(.binding)){.ended};
|
|
711
|
+
const BND = [_]Optional(Observation(.binding)){ .continuing, .ended };
|
|
712
|
+
|
|
713
|
+
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
714
|
+
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
715
|
+
pub fn binding(
|
|
716
|
+
action: Action,
|
|
717
|
+
duration: Duration,
|
|
718
|
+
parent: Optional(bool),
|
|
719
|
+
) []const Optional(Observation(.binding)) {
|
|
720
|
+
if (parent == .true) return &BND_NONE;
|
|
721
|
+
return switch (action.binding) {
|
|
722
|
+
.None => &BND_NONE,
|
|
723
|
+
.started => &BND_STARTED,
|
|
724
|
+
else => if (duration.binding >= 4) &BND_ENDED else &BND,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const BOOL_NONE = [_]Optional(bool){.None};
|
|
729
|
+
const BOOLS = [_]Optional(bool){ .false, .true };
|
|
730
|
+
|
|
731
|
+
/// Returns a slice with the correct range of values for hits given the `action` state
|
|
732
|
+
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
733
|
+
pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
734
|
+
if (parent == .true) return &BOOL_NONE;
|
|
735
|
+
return if (action.hit == .None) &BOOL_NONE else &BOOLS;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
739
|
+
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
740
|
+
pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
741
|
+
if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
|
|
742
|
+
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/// Returns a slice with the correct range of values for secondary chances hits given the
|
|
746
|
+
/// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
747
|
+
pub fn secondaryChance(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
748
|
+
if (parent == .false) return &BOOL_NONE;
|
|
749
|
+
return if (@field(action, "secondary_chance") == .None) &BOOL_NONE else &BOOLS;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/// The min and max bounds on iteration over damage rolls.
|
|
753
|
+
pub const Range = struct { min: usize, max: usize };
|
|
754
|
+
|
|
755
|
+
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
756
|
+
/// the `parent` (whether the player's Pokémon's move hit).
|
|
757
|
+
pub fn damage(action: Action, parent: Optional(bool)) Range {
|
|
758
|
+
return if (parent == .false or action.damage == 0)
|
|
759
|
+
.{ .min = 0, .max = 1 }
|
|
760
|
+
else
|
|
761
|
+
.{ .min = 217, .max = 256 };
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
765
|
+
/// given the base damage in `summaries`.
|
|
766
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
|
|
767
|
+
if (roll == 0) return roll;
|
|
768
|
+
|
|
769
|
+
const dmg = summaries.get(player.foe()).damage;
|
|
770
|
+
if (dmg.base == 0 or (cap and dmg.capped)) return 255;
|
|
771
|
+
|
|
772
|
+
// Closed form solution for max damage roll provided by Orion Taylor (taylorott)
|
|
773
|
+
return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
777
|
+
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
778
|
+
pub fn confused(
|
|
779
|
+
action: Action,
|
|
780
|
+
parent: Optional(Observation(.confusion)),
|
|
781
|
+
) []const Optional(bool) {
|
|
782
|
+
const done = parent == .None or parent == .ended;
|
|
783
|
+
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
787
|
+
/// and the state of the `parent` (whether the player's Pokémon was confused).
|
|
788
|
+
pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
789
|
+
if (parent == .true) return &BOOL_NONE;
|
|
790
|
+
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const SLOT_NONE = [_]u3{0};
|
|
794
|
+
const SLOT = [_]u3{ 1, 2, 3, 4 };
|
|
795
|
+
|
|
796
|
+
/// Returns a slice with a range of values for move slots given the `action` state
|
|
797
|
+
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
798
|
+
///
|
|
799
|
+
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
800
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
|
|
801
|
+
if (parent == .false) return &SLOT_NONE;
|
|
802
|
+
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const MULTI_NONE = [_]u3{0};
|
|
806
|
+
const MULTI = [_]u3{ 2, 3, 4, 5 };
|
|
807
|
+
|
|
808
|
+
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
809
|
+
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
810
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
|
|
811
|
+
if (parent == .false) return &MULTI_NONE;
|
|
812
|
+
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const PSYWAVE_NONE = [_]u8{0};
|
|
816
|
+
const PSYWAVE = init: {
|
|
817
|
+
var rolls: [150]u8 = undefined;
|
|
818
|
+
for (0..150) |i| rolls[i] = i + 1;
|
|
819
|
+
break :init rolls;
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
/// Returns a slice with the correct range of values for psywave given the `action` state,
|
|
823
|
+
/// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
824
|
+
pub fn psywave(
|
|
825
|
+
action: Action,
|
|
826
|
+
side: *data.Side,
|
|
827
|
+
parent: Optional(bool),
|
|
828
|
+
) []const u8 {
|
|
829
|
+
if (parent == .false) return &PSYWAVE_NONE;
|
|
830
|
+
return if (action.psywave == 0)
|
|
831
|
+
&PSYWAVE_NONE
|
|
832
|
+
else
|
|
833
|
+
PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const MOVE_NONE = [_]data.Move{.None};
|
|
837
|
+
|
|
838
|
+
/// Returns a slice with the correct range of values for metronome given the `action` state.
|
|
839
|
+
pub fn metronome(action: Action) []const data.Move {
|
|
840
|
+
return if (action.metronome == .None) &MOVE_NONE else &data.Move.METRONOME;
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
test "Rolls.speedTie" {
|
|
845
|
+
const actions: Actions = .{ .p1 = .{ .speed_tie = .P2 } };
|
|
846
|
+
try expectEqualSlices(Optional(Player), &.{ .P1, .P2 }, Rolls.speedTie(actions.p1));
|
|
847
|
+
try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
test "Rolls.sleep" {
|
|
851
|
+
return error.SkipZigTest; // TODO
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
test "Rolls.disable" {
|
|
855
|
+
return error.SkipZigTest; // TODO
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
test "Rolls.confusion" {
|
|
859
|
+
return error.SkipZigTest; // TODO
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
test "Rolls.atacking" {
|
|
863
|
+
return error.SkipZigTest; // TODO
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
test "Rolls.binding" {
|
|
867
|
+
return error.SkipZigTest; // TODO
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
test "Rolls.damage" {
|
|
871
|
+
const actions: Actions = .{ .p2 = .{ .damage = 221 } };
|
|
872
|
+
try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
|
|
873
|
+
try expectEqual(Rolls.Range{ .min = 217, .max = 256 }, Rolls.damage(actions.p2, .None));
|
|
874
|
+
try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p2, .false));
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
test "Rolls.coalesce" {
|
|
878
|
+
var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
879
|
+
try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
|
|
880
|
+
try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
|
|
881
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
|
|
882
|
+
summaries.p1.damage.final = 74;
|
|
883
|
+
try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
|
|
884
|
+
try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
test "Rolls.hit" {
|
|
888
|
+
const actions: Actions = .{ .p2 = .{ .hit = .true } };
|
|
889
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.hit(actions.p1, .None));
|
|
890
|
+
try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.hit(actions.p2, .None));
|
|
891
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.hit(actions.p2, .true));
|
|
892
|
+
try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.hit(actions.p2, .false));
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
test "Rolls.secondaryChance" {
|
|
896
|
+
const actions: Actions = .{ .p1 = .{ .secondary_chance = .true } };
|
|
897
|
+
try expectEqualSlices(
|
|
898
|
+
Optional(bool),
|
|
899
|
+
&.{ .false, .true },
|
|
900
|
+
Rolls.secondaryChance(actions.p1, .None),
|
|
901
|
+
);
|
|
902
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.secondaryChance(actions.p1, .false));
|
|
903
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.secondaryChance(actions.p2, .None));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
test "Rolls.criticalHit" {
|
|
907
|
+
const actions: Actions = .{ .p1 = .{ .critical_hit = .true } };
|
|
908
|
+
try expectEqualSlices(
|
|
909
|
+
Optional(bool),
|
|
910
|
+
&.{ .false, .true },
|
|
911
|
+
Rolls.criticalHit(actions.p1, .None),
|
|
912
|
+
);
|
|
913
|
+
try expectEqualSlices(
|
|
914
|
+
Optional(bool),
|
|
915
|
+
if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
|
|
916
|
+
Rolls.criticalHit(actions.p1, .false),
|
|
917
|
+
);
|
|
918
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
test "Rolls.confused" {
|
|
922
|
+
const actions: Actions = .{ .p2 = .{ .confused = .true } };
|
|
923
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
|
|
924
|
+
try expectEqualSlices(
|
|
925
|
+
Optional(bool),
|
|
926
|
+
&.{ .false, .true },
|
|
927
|
+
Rolls.confused(actions.p2, .continuing),
|
|
928
|
+
);
|
|
929
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
test "Rolls.paralyzed" {
|
|
933
|
+
const actions: Actions = .{ .p2 = .{ .paralyzed = .true } };
|
|
934
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p1, .None));
|
|
935
|
+
try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.paralyzed(actions.p2, .None));
|
|
936
|
+
try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.paralyzed(actions.p2, .false));
|
|
937
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p2, .true));
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
test "Rolls.moveSlot" {
|
|
941
|
+
const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
|
|
942
|
+
try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p1, .None));
|
|
943
|
+
try expectEqualSlices(u3, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
|
|
944
|
+
try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p2, .false));
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
test "Rolls.multiHit" {
|
|
948
|
+
const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
|
|
949
|
+
try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p1, .None));
|
|
950
|
+
try expectEqualSlices(u3, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
|
|
951
|
+
try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p2, .false));
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
test "Rolls.metronome" {
|
|
955
|
+
const actions: Actions = .{ .p2 = .{ .metronome = .Surf } };
|
|
956
|
+
try expectEqualSlices(data.Move, &.{.None}, Rolls.metronome(actions.p1));
|
|
957
|
+
try expectEqual(@as(data.Move, @enumFromInt(24)), Rolls.metronome(actions.p2)[23]);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
test "Rolls.psywave" {
|
|
961
|
+
const actions: Actions = .{ .p2 = .{ .psywave = 79 } };
|
|
962
|
+
var side = helpers.Side.init(&[_]helpers.Pokemon{.{
|
|
963
|
+
.species = .Bulbasaur,
|
|
964
|
+
.level = 100,
|
|
965
|
+
.moves = &[_]data.Move{.Tackle},
|
|
966
|
+
}});
|
|
967
|
+
|
|
968
|
+
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
969
|
+
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
970
|
+
try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
|
|
971
|
+
side.stored().level = 81;
|
|
972
|
+
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
973
|
+
try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
|
|
974
|
+
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
975
|
+
}
|