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