@pkmn/engine 0.1.0-dev.80019d10 → 0.1.0-dev.8058a1d0
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 +59 -24
- 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 +39 -71
- 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 +27 -5
- 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 +42 -91
- 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 +109 -107
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +84 -75
- 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 +259 -112
- package/package.json +61 -132
- package/src/bin/install-pkmn-engine +47 -21
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/bindings/c.zig +200 -0
- package/src/lib/{binding → bindings}/node.zig +15 -32
- package/src/lib/bindings/wasm.zig +50 -0
- package/src/lib/common/array.zig +70 -0
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/debug.zig +33 -28
- package/src/lib/common/js.zig +3 -7
- package/src/lib/common/optional.zig +12 -18
- package/src/lib/common/options.zig +6 -0
- package/src/lib/common/protocol.zig +256 -224
- package/src/lib/common/rational.zig +340 -253
- package/src/lib/common/rng.zig +7 -9
- package/src/lib/common/util.zig +33 -1
- package/src/lib/gen1/calc.zig +724 -175
- package/src/lib/gen1/chance.zig +453 -291
- package/src/lib/gen1/data/moves.zig +2 -4
- package/src/lib/gen1/data/species.zig +2 -4
- package/src/lib/gen1/data/types.zig +3 -4
- package/src/lib/gen1/data.zig +29 -32
- package/src/lib/gen1/helpers.zig +91 -549
- package/src/lib/gen1/mechanics.zig +417 -294
- package/src/lib/gen2/calc.zig +35 -28
- package/src/lib/gen2/chance.zig +85 -503
- 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 +27 -29
- package/src/lib/gen2/generated.zig +19 -26
- package/src/lib/gen2/helpers.zig +45 -26
- package/src/lib/gen2/mechanics.zig +234 -180
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +14 -0
- package/src/lib/pkmn.zig +125 -29
- package/src/lib/wasm.zig +13 -0
- 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/lib/binding/wasm.zig +0 -12
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -329
- package/src/test/display/pkmn.html.tmpl +0 -368
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
|
|
3
|
-
const assert = std.debug.assert;
|
|
4
|
-
const print = std.debug.print;
|
|
5
|
-
|
|
6
|
-
const expect = std.testing.expect;
|
|
7
|
-
const expectEqual = std.testing.expectEqual;
|
|
8
|
-
|
|
9
1
|
const DEBUG = @import("../common/debug.zig").print;
|
|
2
|
+
|
|
3
|
+
const array = @import("../common/array.zig");
|
|
4
|
+
const common = @import("../common/data.zig");
|
|
5
|
+
const data = @import("data.zig");
|
|
6
|
+
const optional = @import("../common/optional.zig");
|
|
10
7
|
const options = @import("../common/options.zig");
|
|
11
8
|
const rational = @import("../common/rational.zig");
|
|
9
|
+
const std = @import("std");
|
|
12
10
|
const util = @import("../common/util.zig");
|
|
13
11
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const data = @import("./data.zig");
|
|
18
|
-
|
|
12
|
+
const Array = array.Array;
|
|
13
|
+
const assert = std.debug.assert;
|
|
19
14
|
const enabled = options.chance;
|
|
20
|
-
const
|
|
21
|
-
|
|
15
|
+
const expect = std.testing.expect;
|
|
16
|
+
const expectEqual = std.testing.expectEqual;
|
|
17
|
+
const isPointerTo = util.isPointerTo;
|
|
22
18
|
const Move = data.Move;
|
|
23
19
|
const MoveSlot = data.MoveSlot;
|
|
20
|
+
const Optional = optional.Optional;
|
|
21
|
+
const Player = common.Player;
|
|
22
|
+
const PointerType = util.PointerType;
|
|
23
|
+
const print = std.debug.print;
|
|
24
|
+
const showdown = options.showdown;
|
|
24
25
|
|
|
25
26
|
/// Actions taken by a hypothetical "chance player" that convey information about which RNG events
|
|
26
27
|
/// were observed during a Generation I battle `update`. This can additionally be provided as input
|
|
@@ -37,17 +38,11 @@ pub const Actions = extern struct {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/// Returns the `Action` for the given `player`.
|
|
40
|
-
pub fn get(self: anytype, player: Player)
|
|
41
|
-
assert(
|
|
41
|
+
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
|
|
42
|
+
assert(isPointerTo(self, Actions));
|
|
42
43
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
43
44
|
}
|
|
44
45
|
|
|
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
46
|
/// Returns true if `a` is equal to `b`.
|
|
52
47
|
pub fn eql(a: Actions, b: Actions) bool {
|
|
53
48
|
return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
|
|
@@ -57,20 +52,15 @@ pub const Actions = extern struct {
|
|
|
57
52
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
58
53
|
/// set to the same value).
|
|
59
54
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
60
|
-
inline for (@typeInfo(Actions).
|
|
61
|
-
inline for (@typeInfo(Action).
|
|
55
|
+
inline for (@typeInfo(Actions).@"struct".fields) |player| {
|
|
56
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
62
57
|
const a_val = @field(@field(a, player.name), field.name);
|
|
63
58
|
const b_val = @field(@field(b, player.name), field.name);
|
|
64
59
|
|
|
65
60
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
66
|
-
|
|
67
|
-
if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
.Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
61
|
+
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
72
62
|
return false,
|
|
73
|
-
.
|
|
63
|
+
.int => if ((a_val > 0) != (b_val > 0)) return false,
|
|
74
64
|
else => unreachable,
|
|
75
65
|
}
|
|
76
66
|
}
|
|
@@ -78,7 +68,26 @@ pub const Actions = extern struct {
|
|
|
78
68
|
return true;
|
|
79
69
|
}
|
|
80
70
|
|
|
81
|
-
|
|
71
|
+
/// TODO
|
|
72
|
+
pub fn matchesAny(a: Actions, bs: []Actions, i: usize) bool {
|
|
73
|
+
for (bs, 0..) |b, j| {
|
|
74
|
+
// TODO: is skipping this redundant check worth it?
|
|
75
|
+
if (i == j) continue;
|
|
76
|
+
if (b.matches(a)) return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// TODO
|
|
82
|
+
pub fn relax(actions: Actions) Actions {
|
|
83
|
+
if (!options.overwrite) return actions;
|
|
84
|
+
var a = actions;
|
|
85
|
+
if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
|
|
86
|
+
if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
|
|
87
|
+
return a;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
|
|
82
91
|
try writer.writeAll("<P1 = ");
|
|
83
92
|
try self.p1.fmt(writer, shape);
|
|
84
93
|
try writer.writeAll(", P2 = ");
|
|
@@ -86,8 +95,7 @@ pub const Actions = extern struct {
|
|
|
86
95
|
try writer.writeAll(">");
|
|
87
96
|
}
|
|
88
97
|
|
|
89
|
-
pub fn format(a: Actions,
|
|
90
|
-
_ = .{ f, o };
|
|
98
|
+
pub fn format(a: Actions, w: *std.Io.Writer) !void {
|
|
91
99
|
try fmt(a, w, false);
|
|
92
100
|
}
|
|
93
101
|
};
|
|
@@ -96,9 +104,6 @@ test Actions {
|
|
|
96
104
|
const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
|
|
97
105
|
const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
|
|
98
106
|
const c: Actions = .{ .p1 = .{ .hit = .true } };
|
|
99
|
-
const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
|
|
100
|
-
const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
101
|
-
const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
102
107
|
|
|
103
108
|
try expect(a.eql(a));
|
|
104
109
|
try expect(!a.eql(b));
|
|
@@ -111,8 +116,43 @@ test Actions {
|
|
|
111
116
|
try expect(b.matches(a));
|
|
112
117
|
try expect(!a.matches(c));
|
|
113
118
|
try expect(!c.matches(a));
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Observation made about a duration - whether the duration has started, been continued, or ended.
|
|
122
|
+
pub fn Observation(comptime field: Duration.Field) type {
|
|
123
|
+
return switch (field) {
|
|
124
|
+
.attacking, .binding => enum(u2) {
|
|
125
|
+
started,
|
|
126
|
+
continuing,
|
|
127
|
+
ended,
|
|
128
|
+
},
|
|
129
|
+
.sleep, .disable => enum(u3) {
|
|
130
|
+
started,
|
|
131
|
+
continuing,
|
|
132
|
+
ended,
|
|
133
|
+
// TODO haze support
|
|
134
|
+
unused1,
|
|
135
|
+
unused2,
|
|
136
|
+
unused3,
|
|
137
|
+
unused4,
|
|
138
|
+
},
|
|
139
|
+
.confusion => enum(u4) {
|
|
140
|
+
started,
|
|
141
|
+
continuing,
|
|
142
|
+
ended,
|
|
143
|
+
/// An indicator that an existing confusion duration has been overwritten by a new
|
|
144
|
+
/// confusion duration due to a Thrashing move ending ("hidden" confusion). This is not
|
|
145
|
+
/// immediately observable to an opponent, to compute probabilities from an opponent's
|
|
146
|
+
/// point of view based solely on public information extra work must be done to correct
|
|
147
|
+
/// for the information leak
|
|
148
|
+
overwritten,
|
|
149
|
+
// TODO haze support
|
|
150
|
+
unused1,
|
|
151
|
+
unused2,
|
|
152
|
+
unused3,
|
|
153
|
+
unused4,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
116
156
|
}
|
|
117
157
|
|
|
118
158
|
/// Information about the RNG that was observed during a Generation I battle `update` for a
|
|
@@ -139,14 +179,25 @@ pub const Action = packed struct(u64) {
|
|
|
139
179
|
/// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
|
|
140
180
|
duration: u4 = 0,
|
|
141
181
|
|
|
142
|
-
///
|
|
143
|
-
|
|
182
|
+
/// TODO
|
|
183
|
+
sleep: Optional(Observation(.sleep)) = .None,
|
|
184
|
+
/// TODO
|
|
185
|
+
confusion: Optional(Observation(.confusion)) = .None,
|
|
186
|
+
/// TODO
|
|
187
|
+
disable: Optional(Observation(.disable)) = .None,
|
|
188
|
+
/// TODO
|
|
189
|
+
attacking: Optional(Observation(.attacking)) = .None,
|
|
190
|
+
/// TODO
|
|
191
|
+
binding: Optional(Observation(.binding)) = .None,
|
|
144
192
|
|
|
145
193
|
/// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
|
|
146
194
|
/// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
|
|
147
|
-
move_slot:
|
|
195
|
+
move_slot: u3 = 0,
|
|
148
196
|
/// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
|
|
149
|
-
multi_hit:
|
|
197
|
+
multi_hit: u3 = 0,
|
|
198
|
+
|
|
199
|
+
/// TODO
|
|
200
|
+
pp: u4 = 0,
|
|
150
201
|
|
|
151
202
|
/// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
|
|
152
203
|
psywave: u8 = 0,
|
|
@@ -154,39 +205,21 @@ pub const Action = packed struct(u64) {
|
|
|
154
205
|
/// If not None, the Move to return for Rolls.metronome.
|
|
155
206
|
metronome: Move = .None,
|
|
156
207
|
|
|
157
|
-
pub const DURATIONS: u64 = 0x000000FFFF000000;
|
|
158
|
-
|
|
159
208
|
pub const Field = std.meta.FieldEnum(Action);
|
|
160
209
|
|
|
161
|
-
|
|
162
|
-
pub fn reset(self: *Action) void {
|
|
163
|
-
self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
|
|
167
|
-
_ = .{ f, o };
|
|
210
|
+
pub fn format(a: Action, w: *std.Io.Writer) !void {
|
|
168
211
|
try fmt(a, w, false);
|
|
169
212
|
}
|
|
170
213
|
|
|
171
|
-
|
|
214
|
+
const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
|
|
215
|
+
|
|
216
|
+
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
172
217
|
try writer.writeByte('(');
|
|
173
218
|
var printed = false;
|
|
174
|
-
inline for (@typeInfo(Action).
|
|
219
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
175
220
|
const val = @field(self, field.name);
|
|
176
221
|
switch (@typeInfo(@TypeOf(val))) {
|
|
177
|
-
|
|
178
|
-
const v = @field(val, f.name);
|
|
179
|
-
if (v != 0) {
|
|
180
|
-
if (printed) try writer.writeAll(", ");
|
|
181
|
-
if (shape) {
|
|
182
|
-
try writer.print("{s}:?", .{f.name});
|
|
183
|
-
} else {
|
|
184
|
-
try writer.print("{s}:{d}", .{ f.name, v });
|
|
185
|
-
}
|
|
186
|
-
printed = true;
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
.Enum => if (val != .None) {
|
|
222
|
+
.@"enum" => if (val != .None) {
|
|
190
223
|
if (printed) try writer.writeAll(", ");
|
|
191
224
|
if (shape) {
|
|
192
225
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -195,12 +228,14 @@ pub const Action = packed struct(u64) {
|
|
|
195
228
|
if (val == .false) "!" else "",
|
|
196
229
|
field.name,
|
|
197
230
|
});
|
|
231
|
+
} else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
|
|
232
|
+
try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field.name });
|
|
198
233
|
} else {
|
|
199
234
|
try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
|
|
200
235
|
}
|
|
201
236
|
printed = true;
|
|
202
237
|
},
|
|
203
|
-
.
|
|
238
|
+
.int => if (val != 0) {
|
|
204
239
|
if (printed) try writer.writeAll(", ");
|
|
205
240
|
if (shape) {
|
|
206
241
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -216,43 +251,75 @@ pub const Action = packed struct(u64) {
|
|
|
216
251
|
}
|
|
217
252
|
};
|
|
218
253
|
|
|
219
|
-
test Action {
|
|
220
|
-
var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
|
|
221
|
-
a.reset();
|
|
222
|
-
|
|
223
|
-
try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
254
|
/// TODO
|
|
227
255
|
pub const Durations = extern struct {
|
|
228
|
-
///
|
|
256
|
+
/// TODO
|
|
229
257
|
p1: Duration = .{},
|
|
230
|
-
///
|
|
258
|
+
/// TODO
|
|
231
259
|
p2: Duration = .{},
|
|
232
260
|
|
|
233
261
|
comptime {
|
|
234
|
-
assert(@sizeOf(Durations) ==
|
|
262
|
+
assert(@sizeOf(Durations) == 8);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// TODO
|
|
266
|
+
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
|
|
267
|
+
assert(isPointerTo(self, Durations));
|
|
268
|
+
return if (player == .P1) &self.p1 else &self.p2;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
pub fn format(self: Durations, writer: *std.Io.Writer) !void {
|
|
272
|
+
try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
|
|
235
273
|
}
|
|
236
274
|
};
|
|
237
275
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
/// The number of turns a Pokémon has been observed to be confused.
|
|
276
|
+
pub const Sleeps = Array(6, u3);
|
|
277
|
+
|
|
278
|
+
/// TODO
|
|
279
|
+
pub const Duration = packed struct(u32) {
|
|
280
|
+
sleeps: Sleeps.T = 0,
|
|
244
281
|
confusion: u3 = 0,
|
|
245
|
-
/// The number of turns a Pokémon has been observed to be disabled.
|
|
246
282
|
disable: u4 = 0,
|
|
247
|
-
/// The number of turns a Pokémon has been observed to be attacking.
|
|
248
283
|
attacking: u3 = 0,
|
|
249
|
-
/// The number of turns a Pokémon has been observed to be binding their opponent.
|
|
250
284
|
binding: u3 = 0,
|
|
251
285
|
|
|
252
|
-
|
|
286
|
+
_: u1 = 0,
|
|
287
|
+
|
|
288
|
+
pub const Field = enum { attacking, binding, sleep, disable, confusion };
|
|
289
|
+
|
|
290
|
+
pub fn format(
|
|
291
|
+
self: Duration,
|
|
292
|
+
writer: *std.Io.Writer,
|
|
293
|
+
) !void {
|
|
294
|
+
try writer.writeByte('(');
|
|
295
|
+
var printed = false;
|
|
296
|
+
inline for (@typeInfo(Duration).@"struct".fields) |field| {
|
|
297
|
+
const val = @field(self, field.name);
|
|
298
|
+
switch (@typeInfo(@TypeOf(val))) {
|
|
299
|
+
.int => if (val != 0) {
|
|
300
|
+
if (printed) try writer.writeAll(", ");
|
|
301
|
+
if (comptime std.mem.eql(u8, field.name, "sleeps")) {
|
|
302
|
+
try writer.print("{s}:[", .{field.name});
|
|
303
|
+
for (0..6) |i| {
|
|
304
|
+
if (i != 0) try writer.writeByte(',');
|
|
305
|
+
try writer.print("{d}", .{Sleeps.get(val, i)});
|
|
306
|
+
}
|
|
307
|
+
try writer.writeAll("]");
|
|
308
|
+
} else {
|
|
309
|
+
try writer.print("{s}:{d}", .{ field.name, val });
|
|
310
|
+
}
|
|
311
|
+
printed = true;
|
|
312
|
+
},
|
|
313
|
+
else => unreachable,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
try writer.writeByte(')');
|
|
317
|
+
}
|
|
253
318
|
};
|
|
254
319
|
|
|
255
|
-
pub const Commit = enum { hit, miss, binding };
|
|
320
|
+
pub const Commit = enum { hit, miss, binding, err, glitch };
|
|
321
|
+
|
|
322
|
+
const uN = if (options.miss) u8 else u9;
|
|
256
323
|
|
|
257
324
|
/// Tracks chance actions and their associated probability during a Generation I battle update when
|
|
258
325
|
/// `options.chance` is enabled.
|
|
@@ -265,14 +332,8 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
265
332
|
/// The actions taken by a hypothetical "chance player" that convey information about which
|
|
266
333
|
/// RNG events were observed during a battle `update`.
|
|
267
334
|
actions: Actions = .{},
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// other durations which are all tied to volatiles, sleep's counter persists through
|
|
271
|
-
// switching and so we must store it here. The indices of these arrays correspond to the
|
|
272
|
-
// `order` field of a Side. This information could be stored in actions but size is a
|
|
273
|
-
// concern so this is tracked separately as actions only purports to track RNG events
|
|
274
|
-
// observed during a single `update` (not across updates).
|
|
275
|
-
sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
|
|
335
|
+
/// TODO
|
|
336
|
+
durations: Durations = .{},
|
|
276
337
|
|
|
277
338
|
// In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
|
|
278
339
|
// later checks render their result irrelevant (missing when the target is actually immune,
|
|
@@ -290,16 +351,13 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
290
351
|
//
|
|
291
352
|
// Finally, because each player's actions are processed sequentially we only need a single
|
|
292
353
|
// shared structure to track this information (though it needs to be cleared appropriately)
|
|
293
|
-
pending: if (showdown) struct {
|
|
294
|
-
hit: bool = false,
|
|
295
|
-
hit_probablity: u8 = 0,
|
|
296
|
-
} else struct {
|
|
354
|
+
pending: if (showdown) struct {} else struct {
|
|
297
355
|
crit: bool = false,
|
|
298
356
|
crit_probablity: u8 = 0,
|
|
299
357
|
damage_roll: u8 = 0,
|
|
300
358
|
hit: bool = false,
|
|
301
|
-
hit_probablity:
|
|
302
|
-
|
|
359
|
+
hit_probablity: uN = 0,
|
|
360
|
+
glitch: bool = false,
|
|
303
361
|
} = .{},
|
|
304
362
|
|
|
305
363
|
/// Possible error returned by operations tracking chance probability.
|
|
@@ -307,86 +365,90 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
307
365
|
|
|
308
366
|
/// Convenience helper to clear fields which typically should be cleared between updates.
|
|
309
367
|
pub fn reset(self: *Self) void {
|
|
368
|
+
if (!enabled) return;
|
|
369
|
+
|
|
310
370
|
self.probability.reset();
|
|
311
|
-
self.actions.
|
|
371
|
+
self.actions = .{};
|
|
312
372
|
self.pending = .{};
|
|
313
373
|
}
|
|
314
374
|
|
|
315
375
|
pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
|
|
376
|
+
if (!enabled) return;
|
|
377
|
+
|
|
378
|
+
assert(!showdown);
|
|
379
|
+
|
|
380
|
+
if (kind == .glitch and !self.pending.glitch) return;
|
|
381
|
+
|
|
316
382
|
var action = self.actions.get(player);
|
|
317
383
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
action.duration = @intCast(self.pending.binding);
|
|
324
|
-
assert(action.durations.binding == 0);
|
|
325
|
-
action.durations.binding = 1;
|
|
326
|
-
}
|
|
327
|
-
if (kind == .binding) return;
|
|
328
|
-
} else assert(kind != .binding);
|
|
384
|
+
if (kind == .err and self.pending.crit_probablity != 0) {
|
|
385
|
+
try self.probability.update(self.pending.crit_probablity, 256);
|
|
386
|
+
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
329
389
|
|
|
330
390
|
// Always commit the hit result if we make it here (commit won't be called at all if the
|
|
331
|
-
// target is immune
|
|
391
|
+
// target is immune*/behind a sub/etc)
|
|
332
392
|
if (self.pending.hit_probablity != 0) {
|
|
333
393
|
try self.probability.update(self.pending.hit_probablity, 256);
|
|
334
394
|
action.hit = if (self.pending.hit) .true else .false;
|
|
335
395
|
}
|
|
336
396
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
// to
|
|
342
|
-
|
|
343
|
-
|
|
397
|
+
// If the move actually lands we can commit any past critical hit / damage rolls. Note
|
|
398
|
+
// we can't rely on the presence or absence of a damage roll to determine whether a
|
|
399
|
+
// "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
|
|
400
|
+
// damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
|
|
401
|
+
// passed to the critical hit helper).
|
|
402
|
+
//
|
|
403
|
+
// There is a special edge case with the division-by-zero freeze where we need to commit
|
|
404
|
+
// the status of critical hit roll the occured before the move missed to avoid missing
|
|
405
|
+
// out on the possibility that the alternative branch encountered an error
|
|
406
|
+
if (kind != .hit and !self.pending.glitch) {
|
|
407
|
+
return assert(!self.pending.crit or self.pending.crit_probablity > 0);
|
|
408
|
+
}
|
|
344
409
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
410
|
+
if (self.pending.crit_probablity != 0) {
|
|
411
|
+
try self.probability.update(self.pending.crit_probablity, 256);
|
|
412
|
+
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
413
|
+
} else assert(!self.pending.glitch);
|
|
349
414
|
|
|
415
|
+
if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
|
|
350
416
|
try self.probability.update(1, 39);
|
|
351
417
|
action.damage = self.pending.damage_roll;
|
|
352
418
|
}
|
|
353
419
|
}
|
|
354
420
|
|
|
355
|
-
pub fn
|
|
421
|
+
pub fn glitch(self: *Self) void {
|
|
356
422
|
if (!enabled) return;
|
|
357
423
|
|
|
358
|
-
|
|
424
|
+
assert(!showdown);
|
|
425
|
+
self.pending.glitch = true;
|
|
359
426
|
}
|
|
360
427
|
|
|
361
|
-
pub fn
|
|
428
|
+
pub fn clearPending(self: *Self) void {
|
|
362
429
|
if (!enabled) return;
|
|
363
430
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (status) action.durations.sleep = 0;
|
|
367
|
-
action.durations.confusion = 0;
|
|
368
|
-
action.durations.disable = 0;
|
|
369
|
-
} else {
|
|
370
|
-
action.durations.attacking = 0;
|
|
371
|
-
action.durations.binding = 0;
|
|
372
|
-
}
|
|
431
|
+
assert(!showdown);
|
|
432
|
+
self.pending = .{};
|
|
373
433
|
}
|
|
374
434
|
|
|
375
|
-
pub fn switched(self: *Self, player: Player,
|
|
435
|
+
pub fn switched(self: *Self, player: Player, slot: u8) void {
|
|
376
436
|
if (!enabled) return;
|
|
377
437
|
|
|
378
|
-
assert(
|
|
379
|
-
assert(out >= 1 and out <= 6);
|
|
438
|
+
assert(slot >= 1 and slot <= 6);
|
|
380
439
|
|
|
381
|
-
var
|
|
440
|
+
var d = self.durations.get(player);
|
|
382
441
|
|
|
383
|
-
const
|
|
384
|
-
|
|
442
|
+
const out = Sleeps.get(d.sleeps, 0);
|
|
443
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
|
|
444
|
+
d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
|
|
385
445
|
|
|
386
|
-
|
|
387
|
-
|
|
446
|
+
d.confusion = 0;
|
|
447
|
+
d.disable = 0;
|
|
448
|
+
d.attacking = 0;
|
|
449
|
+
d.binding = 0;
|
|
388
450
|
|
|
389
|
-
self.
|
|
451
|
+
self.durations.get(player.foe()).binding = 0;
|
|
390
452
|
}
|
|
391
453
|
|
|
392
454
|
pub fn speedTie(self: *Self, p1: bool) Error!void {
|
|
@@ -397,12 +459,17 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
397
459
|
self.actions.p2.speed_tie = self.actions.p1.speed_tie;
|
|
398
460
|
}
|
|
399
461
|
|
|
400
|
-
pub fn hit(self: *Self, ok: bool, accuracy:
|
|
462
|
+
pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
401
463
|
if (!enabled) return;
|
|
402
464
|
|
|
403
465
|
const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
|
|
404
|
-
|
|
405
|
-
|
|
466
|
+
if (showdown) {
|
|
467
|
+
try self.probability.update(p, 256);
|
|
468
|
+
self.actions.get(player).hit = if (ok) .true else .false;
|
|
469
|
+
} else {
|
|
470
|
+
self.pending.hit = ok;
|
|
471
|
+
self.pending.hit_probablity = p;
|
|
472
|
+
}
|
|
406
473
|
}
|
|
407
474
|
|
|
408
475
|
pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -480,108 +547,188 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
480
547
|
self.actions.get(player).multi_hit = n;
|
|
481
548
|
}
|
|
482
549
|
|
|
483
|
-
pub fn duration(
|
|
550
|
+
pub fn duration(self: *Self, player: Player, turns: u4) void {
|
|
551
|
+
if (!enabled) return;
|
|
552
|
+
|
|
553
|
+
self.actions.get(player).duration = if (options.key) 2 else turns;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
pub fn observe(
|
|
484
557
|
self: *Self,
|
|
485
|
-
comptime field:
|
|
558
|
+
comptime field: Action.Field,
|
|
486
559
|
player: Player,
|
|
487
|
-
|
|
488
|
-
turns: u4,
|
|
560
|
+
obs: Optional(Observation(.confusion)),
|
|
489
561
|
) void {
|
|
490
562
|
if (!enabled) return;
|
|
491
563
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
(
|
|
498
|
-
|
|
499
|
-
|
|
564
|
+
var a = self.actions.get(player);
|
|
565
|
+
const val = @field(a, @tagName(field));
|
|
566
|
+
switch (obs) {
|
|
567
|
+
.overwritten => {
|
|
568
|
+
assert(field == .confusion);
|
|
569
|
+
assert(val == .started or val == .continuing);
|
|
570
|
+
assert(a.duration > 0);
|
|
571
|
+
a.confusion = obs;
|
|
572
|
+
self.durations.get(player).confusion = 1;
|
|
573
|
+
},
|
|
574
|
+
.None => {
|
|
575
|
+
@field(a, @tagName(field)) = .None;
|
|
576
|
+
if (field == .sleep) {
|
|
577
|
+
var d = self.durations.get(player);
|
|
578
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
579
|
+
} else {
|
|
580
|
+
@field(self.durations.get(player), @tagName(field)) = 0;
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
.started => {
|
|
584
|
+
assert(val == .None or val == .ended);
|
|
585
|
+
assert(switch (field) {
|
|
586
|
+
.confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
|
|
587
|
+
.sleep, .disable => self.actions.get(player.foe()).duration > 0,
|
|
588
|
+
else => a.duration > 0,
|
|
589
|
+
});
|
|
590
|
+
@field(a, @tagName(field)) = .started;
|
|
591
|
+
if (field == .sleep) {
|
|
592
|
+
var d = self.durations.get(player);
|
|
593
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 1);
|
|
594
|
+
} else {
|
|
595
|
+
@field(self.durations.get(player), @tagName(field)) = 1;
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
else => unreachable,
|
|
500
599
|
}
|
|
501
600
|
}
|
|
502
601
|
|
|
503
|
-
pub fn sleep(self: *Self, player: Player,
|
|
602
|
+
pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
|
|
504
603
|
if (!enabled) return;
|
|
505
604
|
|
|
506
|
-
var
|
|
507
|
-
|
|
508
|
-
|
|
605
|
+
var a = self.actions.get(player);
|
|
606
|
+
assert(a.sleep == .None or a.sleep == .started);
|
|
607
|
+
a.sleep = obs;
|
|
608
|
+
|
|
609
|
+
var d = self.durations.get(player);
|
|
610
|
+
const n = Sleeps.get(d.sleeps, 0);
|
|
611
|
+
|
|
612
|
+
if (obs == .None) {
|
|
613
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
614
|
+
} else if (obs == .ended) {
|
|
509
615
|
assert(n >= 1 and n <= 7);
|
|
510
616
|
if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
|
|
511
|
-
|
|
617
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
512
618
|
} else {
|
|
619
|
+
assert(obs == .continuing);
|
|
513
620
|
assert(n >= 1 and n < 7);
|
|
514
621
|
try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
|
|
515
|
-
|
|
622
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
|
|
516
623
|
}
|
|
517
624
|
}
|
|
518
625
|
|
|
519
|
-
pub fn confusion(
|
|
626
|
+
pub fn confusion(
|
|
627
|
+
self: *Self,
|
|
628
|
+
player: Player,
|
|
629
|
+
obs: Optional(Observation(.confusion)),
|
|
630
|
+
) Error!void {
|
|
520
631
|
if (!enabled) return;
|
|
521
632
|
|
|
522
|
-
var
|
|
523
|
-
|
|
524
|
-
|
|
633
|
+
var a = self.actions.get(player);
|
|
634
|
+
assert(a.confusion == .None or a.confusion == .started);
|
|
635
|
+
a.confusion = obs;
|
|
636
|
+
|
|
637
|
+
var d = self.durations.get(player);
|
|
638
|
+
const n = d.confusion;
|
|
639
|
+
|
|
640
|
+
if (obs == .ended) {
|
|
525
641
|
assert(n >= 2 and n <= 5);
|
|
526
642
|
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
527
|
-
|
|
643
|
+
d.confusion = 0;
|
|
528
644
|
} else {
|
|
645
|
+
assert(obs == .continuing);
|
|
529
646
|
assert(n >= 1 and n < 5);
|
|
530
647
|
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
531
|
-
|
|
648
|
+
d.confusion += 1;
|
|
532
649
|
}
|
|
533
650
|
}
|
|
534
651
|
|
|
535
|
-
pub fn disable(
|
|
652
|
+
pub fn disable(
|
|
653
|
+
self: *Self,
|
|
654
|
+
player: Player,
|
|
655
|
+
obs: Optional(Observation(.disable)),
|
|
656
|
+
) Error!void {
|
|
536
657
|
if (!enabled) return;
|
|
537
658
|
|
|
538
|
-
var
|
|
539
|
-
|
|
540
|
-
|
|
659
|
+
var a = self.actions.get(player);
|
|
660
|
+
assert(a.disable == .None or a.disable == .started);
|
|
661
|
+
a.disable = obs;
|
|
662
|
+
|
|
663
|
+
var d = self.durations.get(player);
|
|
664
|
+
const n = d.disable;
|
|
665
|
+
|
|
666
|
+
if (obs == .ended) {
|
|
541
667
|
assert(n >= 1 and n <= 8);
|
|
542
668
|
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
543
|
-
|
|
669
|
+
d.disable = 0;
|
|
544
670
|
} else {
|
|
671
|
+
assert(obs == .continuing);
|
|
545
672
|
assert(n >= 1 and n < 8);
|
|
546
673
|
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
547
|
-
|
|
674
|
+
d.disable += 1;
|
|
548
675
|
}
|
|
549
676
|
}
|
|
550
677
|
|
|
551
|
-
pub fn attacking(
|
|
678
|
+
pub fn attacking(
|
|
679
|
+
self: *Self,
|
|
680
|
+
player: Player,
|
|
681
|
+
obs: Optional(Observation(.attacking)),
|
|
682
|
+
) Error!void {
|
|
552
683
|
if (!enabled) return;
|
|
553
684
|
|
|
554
|
-
var
|
|
555
|
-
|
|
556
|
-
|
|
685
|
+
var a = self.actions.get(player);
|
|
686
|
+
assert(a.attacking == .None or a.attacking == .started);
|
|
687
|
+
a.attacking = obs;
|
|
688
|
+
|
|
689
|
+
var d = self.durations.get(player);
|
|
690
|
+
const n = d.attacking;
|
|
691
|
+
if (obs == .ended) {
|
|
692
|
+
// "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
557
693
|
assert(n >= 2 and n <= 3);
|
|
558
694
|
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
559
|
-
|
|
695
|
+
d.attacking = 0;
|
|
560
696
|
} else {
|
|
697
|
+
assert(obs == .continuing);
|
|
561
698
|
assert(n >= 1 and n < 3);
|
|
562
699
|
if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
|
|
563
|
-
|
|
700
|
+
d.attacking += 1;
|
|
564
701
|
}
|
|
565
702
|
}
|
|
566
703
|
|
|
567
|
-
pub fn binding(
|
|
704
|
+
pub fn binding(
|
|
705
|
+
self: *Self,
|
|
706
|
+
player: Player,
|
|
707
|
+
obs: Optional(Observation(.binding)),
|
|
708
|
+
) Error!void {
|
|
568
709
|
if (!enabled) return;
|
|
569
710
|
|
|
570
|
-
var
|
|
571
|
-
|
|
711
|
+
var a = self.actions.get(player);
|
|
712
|
+
assert(a.binding == .None or a.binding == .started);
|
|
713
|
+
a.binding = obs;
|
|
714
|
+
|
|
715
|
+
var d = self.durations.get(player);
|
|
716
|
+
const n = d.binding;
|
|
572
717
|
|
|
573
718
|
assert(n > 0);
|
|
574
719
|
const p: u4 = if (n < 3) 3 else 1;
|
|
575
720
|
const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
|
|
576
721
|
|
|
577
|
-
if (
|
|
722
|
+
if (obs == .ended) {
|
|
723
|
+
// "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
578
724
|
assert(n >= 1 and n <= 4);
|
|
579
725
|
if (n != 4) try self.probability.update(p, q);
|
|
580
|
-
|
|
726
|
+
d.binding = 0;
|
|
581
727
|
} else {
|
|
728
|
+
assert(obs == .continuing);
|
|
582
729
|
assert(n >= 1 and n < 4);
|
|
583
730
|
try self.probability.update(q - p, q);
|
|
584
|
-
|
|
731
|
+
d.binding += 1;
|
|
585
732
|
}
|
|
586
733
|
}
|
|
587
734
|
|
|
@@ -598,6 +745,15 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
598
745
|
try self.probability.update(1, Move.METRONOME.len);
|
|
599
746
|
self.actions.get(player).metronome = move;
|
|
600
747
|
}
|
|
748
|
+
|
|
749
|
+
pub fn pp(self: *Self, player: Player, n: u4) bool {
|
|
750
|
+
if (!enabled) return true;
|
|
751
|
+
|
|
752
|
+
var a = self.actions.get(player);
|
|
753
|
+
if (n > 1 and a.pp == n) return false;
|
|
754
|
+
if (a.metronome != .None) a.pp += n;
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
601
757
|
};
|
|
602
758
|
}
|
|
603
759
|
|
|
@@ -620,19 +776,25 @@ test "Chance.speedTie" {
|
|
|
620
776
|
test "Chance.hit" {
|
|
621
777
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
622
778
|
|
|
623
|
-
chance.hit(true, 229);
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
779
|
+
try chance.hit(.P1, true, 229);
|
|
780
|
+
if (!showdown) {
|
|
781
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
782
|
+
try expectValue(Optional(bool).None, chance.actions.p1.hit);
|
|
783
|
+
|
|
784
|
+
try chance.commit(.P1, .hit);
|
|
785
|
+
}
|
|
627
786
|
try expectValue(Optional(bool).true, chance.actions.p1.hit);
|
|
628
787
|
try expectProbability(&chance.probability, 229, 256);
|
|
629
788
|
|
|
630
789
|
chance.reset();
|
|
631
790
|
|
|
632
|
-
chance.hit(false, 255);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
791
|
+
try chance.hit(.P2, false, 255);
|
|
792
|
+
if (!showdown) {
|
|
793
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
794
|
+
try expectValue(Optional(bool).None, chance.actions.p2.hit);
|
|
795
|
+
|
|
796
|
+
try chance.commit(.P2, .miss);
|
|
797
|
+
}
|
|
636
798
|
try expectValue(Optional(bool).false, chance.actions.p2.hit);
|
|
637
799
|
try expectProbability(&chance.probability, 1, 256);
|
|
638
800
|
}
|
|
@@ -648,26 +810,18 @@ test "Chance.criticalHit" {
|
|
|
648
810
|
try expectProbability(&chance.probability, 1, 1);
|
|
649
811
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
650
812
|
|
|
651
|
-
try chance.
|
|
652
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
653
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
654
|
-
|
|
655
|
-
chance.hit(true, 128);
|
|
813
|
+
try chance.hit(.P1, true, 128);
|
|
656
814
|
|
|
657
|
-
try chance.
|
|
658
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
815
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
659
816
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
660
817
|
|
|
661
|
-
chance.probability.reset();
|
|
662
818
|
try chance.damage(.P1, 217);
|
|
663
819
|
|
|
664
|
-
try chance.
|
|
665
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
820
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
666
821
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
667
822
|
|
|
668
|
-
chance.probability.reset();
|
|
669
|
-
|
|
670
823
|
try chance.commit(.P1, .hit);
|
|
824
|
+
|
|
671
825
|
try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
|
|
672
826
|
try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
|
|
673
827
|
}
|
|
@@ -682,20 +836,14 @@ test "Chance.criticalHit" {
|
|
|
682
836
|
try expectProbability(&chance.probability, 1, 1);
|
|
683
837
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
684
838
|
|
|
685
|
-
try chance.
|
|
686
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
687
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
688
|
-
|
|
689
|
-
chance.hit(true, 128);
|
|
839
|
+
try chance.hit(.P1, true, 128);
|
|
690
840
|
|
|
691
|
-
try chance.
|
|
692
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
841
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
693
842
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
694
843
|
|
|
695
|
-
chance.probability.reset();
|
|
696
844
|
try chance.damage(.P1, 217);
|
|
697
|
-
|
|
698
845
|
try chance.commit(.P1, .hit);
|
|
846
|
+
|
|
699
847
|
try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
|
|
700
848
|
try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
|
|
701
849
|
}
|
|
@@ -712,11 +860,8 @@ test "Chance.damage" {
|
|
|
712
860
|
try expectProbability(&chance.probability, 1, 1);
|
|
713
861
|
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
714
862
|
|
|
715
|
-
try chance.commit(.P1, .miss);
|
|
716
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
717
|
-
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
718
|
-
|
|
719
863
|
try chance.commit(.P1, .hit);
|
|
864
|
+
|
|
720
865
|
try expectProbability(&chance.probability, 1, 39);
|
|
721
866
|
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
722
867
|
}
|
|
@@ -770,13 +915,13 @@ test "Chance.moveSlot" {
|
|
|
770
915
|
|
|
771
916
|
try chance.moveSlot(.P2, 2, &ms, 2);
|
|
772
917
|
try expectProbability(&chance.probability, 1, 2);
|
|
773
|
-
try expectValue(@as(
|
|
918
|
+
try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
|
|
774
919
|
|
|
775
920
|
chance.reset();
|
|
776
921
|
|
|
777
922
|
try chance.moveSlot(.P1, 1, &ms, 0);
|
|
778
923
|
try expectProbability(&chance.probability, 1, 3);
|
|
779
|
-
try expectValue(@as(
|
|
924
|
+
try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
|
|
780
925
|
}
|
|
781
926
|
|
|
782
927
|
test "Chance.multiHit" {
|
|
@@ -796,38 +941,31 @@ test "Chance.multiHit" {
|
|
|
796
941
|
test "Chance.duration" {
|
|
797
942
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
798
943
|
|
|
799
|
-
chance.duration(.
|
|
944
|
+
chance.duration(.P1, 2);
|
|
800
945
|
try expectValue(@as(u4, 2), chance.actions.p1.duration);
|
|
801
|
-
try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
|
|
802
946
|
|
|
803
947
|
chance.reset();
|
|
804
948
|
|
|
805
|
-
chance.duration(.
|
|
806
|
-
if (!showdown) {
|
|
807
|
-
try expectValue(@as(u4, 0), chance.actions.p2.duration);
|
|
808
|
-
try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
|
|
809
|
-
try chance.commit(.P2, .hit);
|
|
810
|
-
}
|
|
949
|
+
chance.duration(.P2, 4);
|
|
811
950
|
try expectValue(@as(u4, 4), chance.actions.p2.duration);
|
|
812
|
-
try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
|
|
813
951
|
}
|
|
814
952
|
|
|
815
953
|
test "Chance.sleep" {
|
|
816
954
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
817
955
|
|
|
818
956
|
for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
|
|
819
|
-
chance.
|
|
820
|
-
try chance.sleep(.P1,
|
|
957
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
958
|
+
try chance.sleep(.P1, .ended);
|
|
821
959
|
try expectProbability(&chance.probability, 1, d);
|
|
822
|
-
try expectValue(@as(u3, 0), chance.
|
|
960
|
+
try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
823
961
|
|
|
824
962
|
chance.reset();
|
|
825
963
|
|
|
826
964
|
if (i < 7) {
|
|
827
|
-
chance.
|
|
828
|
-
try chance.sleep(.P1,
|
|
965
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
966
|
+
try chance.sleep(.P1, .continuing);
|
|
829
967
|
try expectProbability(&chance.probability, d - 1, d);
|
|
830
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
968
|
+
try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
831
969
|
|
|
832
970
|
chance.reset();
|
|
833
971
|
}
|
|
@@ -839,19 +977,19 @@ test "Chance.confusion" {
|
|
|
839
977
|
|
|
840
978
|
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
841
979
|
if (i > 1) {
|
|
842
|
-
chance.
|
|
843
|
-
try chance.confusion(.P2,
|
|
980
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
981
|
+
try chance.confusion(.P2, .ended);
|
|
844
982
|
try expectProbability(&chance.probability, 1, d);
|
|
845
|
-
try expectValue(@as(u3, 0), chance.
|
|
983
|
+
try expectValue(@as(u3, 0), chance.durations.p2.confusion);
|
|
846
984
|
|
|
847
985
|
chance.reset();
|
|
848
986
|
}
|
|
849
987
|
|
|
850
988
|
if (i < 5) {
|
|
851
|
-
chance.
|
|
852
|
-
try chance.confusion(.P2,
|
|
989
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
990
|
+
try chance.confusion(.P2, .continuing);
|
|
853
991
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
854
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
992
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
|
|
855
993
|
|
|
856
994
|
chance.reset();
|
|
857
995
|
}
|
|
@@ -862,18 +1000,18 @@ test "Chance.disable" {
|
|
|
862
1000
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
863
1001
|
|
|
864
1002
|
for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
865
|
-
chance.
|
|
866
|
-
try chance.disable(.P1,
|
|
1003
|
+
chance.durations.p1.disable = @intCast(i);
|
|
1004
|
+
try chance.disable(.P1, .ended);
|
|
867
1005
|
try expectProbability(&chance.probability, 1, d);
|
|
868
|
-
try expectValue(@as(u4, 0), chance.
|
|
1006
|
+
try expectValue(@as(u4, 0), chance.durations.p1.disable);
|
|
869
1007
|
|
|
870
1008
|
chance.reset();
|
|
871
1009
|
|
|
872
1010
|
if (i < 8) {
|
|
873
|
-
chance.
|
|
874
|
-
try chance.disable(.P1,
|
|
1011
|
+
chance.durations.p1.disable = @intCast(i);
|
|
1012
|
+
try chance.disable(.P1, .continuing);
|
|
875
1013
|
try expectProbability(&chance.probability, d - 1, d);
|
|
876
|
-
try expectValue(@as(u4, @intCast(i)) + 1, chance.
|
|
1014
|
+
try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
|
|
877
1015
|
|
|
878
1016
|
chance.reset();
|
|
879
1017
|
}
|
|
@@ -885,19 +1023,19 @@ test "Chance.attacking" {
|
|
|
885
1023
|
|
|
886
1024
|
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
887
1025
|
if (i > 1) {
|
|
888
|
-
chance.
|
|
889
|
-
try chance.attacking(.P2,
|
|
1026
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1027
|
+
try chance.attacking(.P2, .ended);
|
|
890
1028
|
try expectProbability(&chance.probability, 1, d);
|
|
891
|
-
try expectValue(@as(u3, 0), chance.
|
|
1029
|
+
try expectValue(@as(u3, 0), chance.durations.p2.attacking);
|
|
892
1030
|
|
|
893
1031
|
chance.reset();
|
|
894
1032
|
}
|
|
895
1033
|
|
|
896
1034
|
if (i < 3) {
|
|
897
|
-
chance.
|
|
898
|
-
try chance.attacking(.P2,
|
|
1035
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1036
|
+
try chance.attacking(.P2, .continuing);
|
|
899
1037
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
900
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1038
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
|
|
901
1039
|
|
|
902
1040
|
chance.reset();
|
|
903
1041
|
}
|
|
@@ -911,18 +1049,18 @@ test "Chance.binding" {
|
|
|
911
1049
|
const qs = [_]u8{ 8, 5, 2, 1 };
|
|
912
1050
|
|
|
913
1051
|
for (ps, qs, 1..5) |p, q, i| {
|
|
914
|
-
chance.
|
|
915
|
-
try chance.binding(.P1,
|
|
1052
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1053
|
+
try chance.binding(.P1, .ended);
|
|
916
1054
|
try expectProbability(&chance.probability, p, q);
|
|
917
|
-
try expectValue(@as(u3, 0), chance.
|
|
1055
|
+
try expectValue(@as(u3, 0), chance.durations.p1.binding);
|
|
918
1056
|
|
|
919
1057
|
chance.reset();
|
|
920
1058
|
|
|
921
1059
|
if (i < 4) {
|
|
922
|
-
chance.
|
|
923
|
-
try chance.binding(.P1,
|
|
1060
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1061
|
+
try chance.binding(.P1, .continuing);
|
|
924
1062
|
try expectProbability(&chance.probability, q - p, q);
|
|
925
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1063
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
|
|
926
1064
|
|
|
927
1065
|
chance.reset();
|
|
928
1066
|
}
|
|
@@ -950,7 +1088,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
|
950
1088
|
|
|
951
1089
|
r.reduce();
|
|
952
1090
|
if (r.p != p or r.q != q) {
|
|
953
|
-
print("expected {d}/{d}, found {}\n", .{ p, q, r });
|
|
1091
|
+
print("expected {d}/{d}, found {f}\n", .{ p, q, r });
|
|
954
1092
|
return error.TestExpectedEqual;
|
|
955
1093
|
}
|
|
956
1094
|
}
|
|
@@ -972,24 +1110,24 @@ const Null = struct {
|
|
|
972
1110
|
_ = .{ self, player, kind };
|
|
973
1111
|
}
|
|
974
1112
|
|
|
975
|
-
pub fn
|
|
1113
|
+
pub fn glitch(self: Null) void {
|
|
976
1114
|
_ = .{self};
|
|
977
1115
|
}
|
|
978
1116
|
|
|
979
|
-
pub fn
|
|
980
|
-
_ = .{
|
|
1117
|
+
pub fn clearPending(self: Null) void {
|
|
1118
|
+
_ = .{self};
|
|
981
1119
|
}
|
|
982
1120
|
|
|
983
|
-
pub fn switched(self: Null, player: Player,
|
|
984
|
-
_ = .{ self, player,
|
|
1121
|
+
pub fn switched(self: Null, player: Player, slot: u8) void {
|
|
1122
|
+
_ = .{ self, player, slot };
|
|
985
1123
|
}
|
|
986
1124
|
|
|
987
1125
|
pub fn speedTie(self: Null, p1: bool) Error!void {
|
|
988
1126
|
_ = .{ self, p1 };
|
|
989
1127
|
}
|
|
990
1128
|
|
|
991
|
-
pub fn hit(self: Null, ok: bool, accuracy:
|
|
992
|
-
_ = .{ self, ok, accuracy };
|
|
1129
|
+
pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
1130
|
+
_ = .{ self, player, ok, accuracy };
|
|
993
1131
|
}
|
|
994
1132
|
|
|
995
1133
|
pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -1020,34 +1158,53 @@ const Null = struct {
|
|
|
1020
1158
|
_ = .{ self, player, n };
|
|
1021
1159
|
}
|
|
1022
1160
|
|
|
1023
|
-
pub fn duration(
|
|
1161
|
+
pub fn duration(self: Null, player: Player, turns: u4) void {
|
|
1162
|
+
_ = .{ self, player, turns };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
pub fn observe(
|
|
1024
1166
|
self: Null,
|
|
1025
|
-
comptime field:
|
|
1167
|
+
comptime field: Action.Field,
|
|
1026
1168
|
player: Player,
|
|
1027
|
-
|
|
1028
|
-
turns: u4,
|
|
1169
|
+
obs: Optional(Observation(.confusion)),
|
|
1029
1170
|
) void {
|
|
1030
|
-
_ = .{ self, field, player,
|
|
1171
|
+
_ = .{ self, field, player, obs };
|
|
1031
1172
|
}
|
|
1032
1173
|
|
|
1033
|
-
pub fn sleep(self: Null, player: Player,
|
|
1034
|
-
_ = .{ self, player,
|
|
1174
|
+
pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
|
|
1175
|
+
_ = .{ self, player, obs };
|
|
1035
1176
|
}
|
|
1036
1177
|
|
|
1037
|
-
pub fn
|
|
1038
|
-
|
|
1178
|
+
pub fn confusion(
|
|
1179
|
+
self: Null,
|
|
1180
|
+
player: Player,
|
|
1181
|
+
obs: Optional(Observation(.confusion)),
|
|
1182
|
+
) Error!void {
|
|
1183
|
+
_ = .{ self, player, obs };
|
|
1039
1184
|
}
|
|
1040
1185
|
|
|
1041
|
-
pub fn
|
|
1042
|
-
|
|
1186
|
+
pub fn disable(
|
|
1187
|
+
self: Null,
|
|
1188
|
+
player: Player,
|
|
1189
|
+
obs: Optional(Observation(.disable)),
|
|
1190
|
+
) Error!void {
|
|
1191
|
+
_ = .{ self, player, obs };
|
|
1043
1192
|
}
|
|
1044
1193
|
|
|
1045
|
-
pub fn attacking(
|
|
1046
|
-
|
|
1194
|
+
pub fn attacking(
|
|
1195
|
+
self: Null,
|
|
1196
|
+
player: Player,
|
|
1197
|
+
obs: Optional(Observation(.attacking)),
|
|
1198
|
+
) Error!void {
|
|
1199
|
+
_ = .{ self, player, obs };
|
|
1047
1200
|
}
|
|
1048
1201
|
|
|
1049
|
-
pub fn binding(
|
|
1050
|
-
|
|
1202
|
+
pub fn binding(
|
|
1203
|
+
self: Null,
|
|
1204
|
+
player: Player,
|
|
1205
|
+
obs: Optional(Observation(.binding)),
|
|
1206
|
+
) Error!void {
|
|
1207
|
+
_ = .{ self, player, obs };
|
|
1051
1208
|
}
|
|
1052
1209
|
|
|
1053
1210
|
pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
|
|
@@ -1057,4 +1214,9 @@ const Null = struct {
|
|
|
1057
1214
|
pub fn metronome(self: Null, player: Player, move: Move) Error!void {
|
|
1058
1215
|
_ = .{ self, player, move };
|
|
1059
1216
|
}
|
|
1217
|
+
|
|
1218
|
+
pub fn pp(self: Null, player: Player, n: u4) bool {
|
|
1219
|
+
_ = .{ self, player, n };
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1060
1222
|
};
|