@pkmn/engine 0.1.0-dev.f918c3bd → 0.1.0-dev.fb6653d6
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 +51 -23
- 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 +53 -0
- package/build/pkg/addon/wasm.js.map +1 -0
- package/build/pkg/addon.d.ts +25 -3
- package/build/pkg/addon.js +39 -71
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/ids.json +12 -12
- package/build/pkg/data/layout.json +17 -4
- 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 +13 -17
- package/build/pkg/gen1.js +8 -12
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +40 -91
- package/build/pkg/index.js +6 -63
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +20 -1
- package/build/pkg/protocol.js +109 -107
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +36 -69
- 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 +142 -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 +303 -101
- package/package.json +61 -132
- package/src/bin/install-pkmn-engine +30 -20
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/{binding → bindings}/node.zig +8 -21
- package/src/lib/bindings/wasm.zig +58 -0
- package/src/lib/common/array.zig +77 -0
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +39 -8
- package/src/lib/common/js.zig +4 -2
- package/src/lib/common/optional.zig +21 -10
- package/src/lib/common/options.zig +6 -0
- package/src/lib/common/protocol.zig +421 -393
- package/src/lib/common/rational.zig +131 -35
- package/src/lib/common/rng.zig +16 -9
- package/src/lib/common/util.zig +50 -1
- package/src/lib/gen1/calc.zig +687 -160
- package/src/lib/gen1/chance.zig +434 -307
- package/src/lib/gen1/data/moves.zig +26 -14
- package/src/lib/gen1/data/species.zig +4 -6
- package/src/lib/gen1/data/types.zig +7 -8
- package/src/lib/gen1/data.zig +44 -47
- package/src/lib/gen1/helpers.zig +91 -562
- package/src/lib/gen1/mechanics.zig +641 -557
- 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 +67 -47
- package/src/lib/gen2/data/species.zig +2 -4
- package/src/lib/gen2/data/types.zig +134 -32
- package/src/lib/gen2/data.zig +56 -54
- package/src/lib/gen2/generated.zig +801 -0
- package/src/lib/gen2/helpers.zig +46 -25
- package/src/lib/gen2/mechanics.zig +2778 -362
- package/src/lib/node.zig +18 -0
- package/src/lib/pkmn.zig +37 -15
- package/src/lib/wasm.zig +17 -0
- 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/binding/wasm.zig +0 -12
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -328
- 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,10 +121,22 @@ 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
142
|
pub const Action = packed struct(u64) {
|
|
@@ -137,12 +161,25 @@ pub const Action = packed struct(u64) {
|
|
|
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,15 +189,8 @@ pub const Action = packed struct(u64) {
|
|
|
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
|
-
/// Perform a reset by clearing fields which should not persist across updates.
|
|
160
|
-
pub inline fn reset(self: *Action) void {
|
|
161
|
-
self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
194
|
pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
|
|
165
195
|
_ = .{ f, o };
|
|
166
196
|
try fmt(a, w, false);
|
|
@@ -169,22 +199,10 @@ pub const Action = packed struct(u64) {
|
|
|
169
199
|
pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
|
|
170
200
|
try writer.writeByte('(');
|
|
171
201
|
var printed = false;
|
|
172
|
-
inline for (@typeInfo(Action)
|
|
202
|
+
inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
|
|
173
203
|
const val = @field(self, field.name);
|
|
174
204
|
switch (@typeInfo(@TypeOf(val))) {
|
|
175
|
-
|
|
176
|
-
const v = @field(val, f.name);
|
|
177
|
-
if (v != 0) {
|
|
178
|
-
if (printed) try writer.writeAll(", ");
|
|
179
|
-
if (shape) {
|
|
180
|
-
try writer.print("{s}:?", .{f.name});
|
|
181
|
-
} else {
|
|
182
|
-
try writer.print("{s}:{d}", .{ f.name, v });
|
|
183
|
-
}
|
|
184
|
-
printed = true;
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
.Enum => if (val != .None) {
|
|
205
|
+
Enum => if (val != .None) {
|
|
188
206
|
if (printed) try writer.writeAll(", ");
|
|
189
207
|
if (shape) {
|
|
190
208
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -193,12 +211,19 @@ pub const Action = packed struct(u64) {
|
|
|
193
211
|
if (val == .false) "!" else "",
|
|
194
212
|
field.name,
|
|
195
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
|
+
});
|
|
196
221
|
} else {
|
|
197
222
|
try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
|
|
198
223
|
}
|
|
199
224
|
printed = true;
|
|
200
225
|
},
|
|
201
|
-
|
|
226
|
+
Int => if (val != 0) {
|
|
202
227
|
if (printed) try writer.writeAll(", ");
|
|
203
228
|
if (shape) {
|
|
204
229
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -214,43 +239,82 @@ pub const Action = packed struct(u64) {
|
|
|
214
239
|
}
|
|
215
240
|
};
|
|
216
241
|
|
|
217
|
-
test Action {
|
|
218
|
-
var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
|
|
219
|
-
a.reset();
|
|
220
|
-
|
|
221
|
-
try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
242
|
/// TODO
|
|
225
243
|
pub const Durations = extern struct {
|
|
226
|
-
///
|
|
244
|
+
/// TODO
|
|
227
245
|
p1: Duration = .{},
|
|
228
|
-
///
|
|
246
|
+
/// TODO
|
|
229
247
|
p2: Duration = .{},
|
|
230
248
|
|
|
231
249
|
comptime {
|
|
232
|
-
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 });
|
|
233
267
|
}
|
|
234
268
|
};
|
|
235
269
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
/// 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,
|
|
242
275
|
confusion: u3 = 0,
|
|
243
|
-
/// The number of turns a Pokémon has been observed to be disabled.
|
|
244
276
|
disable: u4 = 0,
|
|
245
|
-
/// The number of turns a Pokémon has been observed to be attacking.
|
|
246
277
|
attacking: u3 = 0,
|
|
247
|
-
/// The number of turns a Pokémon has been observed to be binding their opponent.
|
|
248
278
|
binding: u3 = 0,
|
|
249
279
|
|
|
250
|
-
|
|
280
|
+
_: u1 = 0,
|
|
281
|
+
|
|
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(')');
|
|
312
|
+
}
|
|
251
313
|
};
|
|
252
314
|
|
|
253
|
-
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;
|
|
254
318
|
|
|
255
319
|
/// Tracks chance actions and their associated probability during a Generation I battle update when
|
|
256
320
|
/// `options.chance` is enabled.
|
|
@@ -263,14 +327,8 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
263
327
|
/// The actions taken by a hypothetical "chance player" that convey information about which
|
|
264
328
|
/// RNG events were observed during a battle `update`.
|
|
265
329
|
actions: Actions = .{},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// other durations which are all tied to volatiles, sleep's counter persists through
|
|
269
|
-
// switching and so we must store it here. The indices of these arrays correspond to the
|
|
270
|
-
// `order` field of a Side. This information could be stored in actions but size is a
|
|
271
|
-
// concern so this is tracked separately as actions only purports to track RNG events
|
|
272
|
-
// observed during a single `update` (not across updates).
|
|
273
|
-
sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
|
|
330
|
+
/// TODO
|
|
331
|
+
durations: Durations = .{},
|
|
274
332
|
|
|
275
333
|
// In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
|
|
276
334
|
// later checks render their result irrelevant (missing when the target is actually immune,
|
|
@@ -288,16 +346,13 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
288
346
|
//
|
|
289
347
|
// Finally, because each player's actions are processed sequentially we only need a single
|
|
290
348
|
// shared structure to track this information (though it needs to be cleared appropriately)
|
|
291
|
-
pending: if (showdown) struct {
|
|
292
|
-
hit: bool = false,
|
|
293
|
-
hit_probablity: u8 = 0,
|
|
294
|
-
} else struct {
|
|
349
|
+
pending: if (showdown) struct {} else struct {
|
|
295
350
|
crit: bool = false,
|
|
296
351
|
crit_probablity: u8 = 0,
|
|
297
352
|
damage_roll: u8 = 0,
|
|
298
353
|
hit: bool = false,
|
|
299
|
-
hit_probablity:
|
|
300
|
-
|
|
354
|
+
hit_probablity: uN = 0,
|
|
355
|
+
glitch: bool = false,
|
|
301
356
|
} = .{},
|
|
302
357
|
|
|
303
358
|
/// Possible error returned by operations tracking chance probability.
|
|
@@ -305,86 +360,90 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
305
360
|
|
|
306
361
|
/// Convenience helper to clear fields which typically should be cleared between updates.
|
|
307
362
|
pub fn reset(self: *Self) void {
|
|
363
|
+
if (!enabled) return;
|
|
364
|
+
|
|
308
365
|
self.probability.reset();
|
|
309
|
-
self.actions.
|
|
366
|
+
self.actions = .{};
|
|
310
367
|
self.pending = .{};
|
|
311
368
|
}
|
|
312
369
|
|
|
313
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
|
+
|
|
314
377
|
var action = self.actions.get(player);
|
|
315
378
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
action.duration = @intCast(self.pending.binding);
|
|
322
|
-
assert(action.durations.binding == 0);
|
|
323
|
-
action.durations.binding = 1;
|
|
324
|
-
}
|
|
325
|
-
if (kind == .binding) return;
|
|
326
|
-
} 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
|
+
}
|
|
327
384
|
|
|
328
385
|
// Always commit the hit result if we make it here (commit won't be called at all if the
|
|
329
|
-
// target is immune
|
|
386
|
+
// target is immune*/behind a sub/etc)
|
|
330
387
|
if (self.pending.hit_probablity != 0) {
|
|
331
388
|
try self.probability.update(self.pending.hit_probablity, 256);
|
|
332
389
|
action.hit = if (self.pending.hit) .true else .false;
|
|
333
390
|
}
|
|
334
391
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
// to
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
}
|
|
342
404
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
347
409
|
|
|
410
|
+
if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
|
|
348
411
|
try self.probability.update(1, 39);
|
|
349
412
|
action.damage = self.pending.damage_roll;
|
|
350
413
|
}
|
|
351
414
|
}
|
|
352
415
|
|
|
353
|
-
pub fn
|
|
416
|
+
pub fn glitch(self: *Self) void {
|
|
354
417
|
if (!enabled) return;
|
|
355
418
|
|
|
356
|
-
|
|
419
|
+
assert(!showdown);
|
|
420
|
+
self.pending.glitch = true;
|
|
357
421
|
}
|
|
358
422
|
|
|
359
|
-
pub fn
|
|
423
|
+
pub fn clearPending(self: *Self) void {
|
|
360
424
|
if (!enabled) return;
|
|
361
425
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (status) action.durations.sleep = 0;
|
|
365
|
-
action.durations.confusion = 0;
|
|
366
|
-
action.durations.disable = 0;
|
|
367
|
-
} else {
|
|
368
|
-
action.durations.attacking = 0;
|
|
369
|
-
action.durations.binding = 0;
|
|
370
|
-
}
|
|
426
|
+
assert(!showdown);
|
|
427
|
+
self.pending = .{};
|
|
371
428
|
}
|
|
372
429
|
|
|
373
|
-
pub fn switched(self: *Self, player: Player,
|
|
430
|
+
pub fn switched(self: *Self, player: Player, slot: u8) void {
|
|
374
431
|
if (!enabled) return;
|
|
375
432
|
|
|
376
|
-
assert(
|
|
377
|
-
assert(out >= 1 and out <= 6);
|
|
433
|
+
assert(slot >= 1 and slot <= 6);
|
|
378
434
|
|
|
379
|
-
var
|
|
435
|
+
var d = self.durations.get(player);
|
|
380
436
|
|
|
381
|
-
const
|
|
382
|
-
|
|
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);
|
|
383
440
|
|
|
384
|
-
|
|
385
|
-
|
|
441
|
+
d.confusion = 0;
|
|
442
|
+
d.disable = 0;
|
|
443
|
+
d.attacking = 0;
|
|
444
|
+
d.binding = 0;
|
|
386
445
|
|
|
387
|
-
self.
|
|
446
|
+
self.durations.get(player.foe()).binding = 0;
|
|
388
447
|
}
|
|
389
448
|
|
|
390
449
|
pub fn speedTie(self: *Self, p1: bool) Error!void {
|
|
@@ -395,12 +454,17 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
395
454
|
self.actions.p2.speed_tie = self.actions.p1.speed_tie;
|
|
396
455
|
}
|
|
397
456
|
|
|
398
|
-
pub fn hit(self: *Self, ok: bool, accuracy:
|
|
457
|
+
pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
399
458
|
if (!enabled) return;
|
|
400
459
|
|
|
401
460
|
const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
|
|
402
|
-
|
|
403
|
-
|
|
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
|
+
}
|
|
404
468
|
}
|
|
405
469
|
|
|
406
470
|
pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -478,108 +542,172 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
478
542
|
self.actions.get(player).multi_hit = n;
|
|
479
543
|
}
|
|
480
544
|
|
|
481
|
-
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(
|
|
482
552
|
self: *Self,
|
|
483
|
-
comptime field:
|
|
553
|
+
comptime field: Action.Field,
|
|
484
554
|
player: Player,
|
|
485
|
-
|
|
486
|
-
turns: u4,
|
|
555
|
+
obs: Optional(Confusion),
|
|
487
556
|
) void {
|
|
488
557
|
if (!enabled) return;
|
|
489
558
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
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,
|
|
498
594
|
}
|
|
499
595
|
}
|
|
500
596
|
|
|
501
|
-
pub fn sleep(self: *Self, player: Player,
|
|
597
|
+
pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
502
598
|
if (!enabled) return;
|
|
503
599
|
|
|
504
|
-
var
|
|
505
|
-
|
|
506
|
-
|
|
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) {
|
|
507
610
|
assert(n >= 1 and n <= 7);
|
|
508
611
|
if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
|
|
509
|
-
|
|
612
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
510
613
|
} else {
|
|
614
|
+
assert(obs == .continuing);
|
|
511
615
|
assert(n >= 1 and n < 7);
|
|
512
616
|
try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
|
|
513
|
-
|
|
617
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
|
|
514
618
|
}
|
|
515
619
|
}
|
|
516
620
|
|
|
517
|
-
pub fn
|
|
621
|
+
pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
|
|
518
622
|
if (!enabled) return;
|
|
519
623
|
|
|
520
|
-
var
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
assert(n >= 1 and n <= 8);
|
|
524
|
-
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
525
|
-
durations.disable = 0;
|
|
526
|
-
} else {
|
|
527
|
-
assert(n >= 1 and n < 8);
|
|
528
|
-
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
529
|
-
durations.disable += 1;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
624
|
+
var a = self.actions.get(player);
|
|
625
|
+
assert(a.confusion == .None or a.confusion == .started);
|
|
626
|
+
a.confusion = obs;
|
|
532
627
|
|
|
533
|
-
|
|
534
|
-
|
|
628
|
+
var d = self.durations.get(player);
|
|
629
|
+
const n = d.confusion;
|
|
535
630
|
|
|
536
|
-
|
|
537
|
-
const n = durations.confusion;
|
|
538
|
-
if (turns == 0) {
|
|
631
|
+
if (obs == .ended) {
|
|
539
632
|
assert(n >= 2 and n <= 5);
|
|
540
633
|
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
541
|
-
|
|
634
|
+
d.confusion = 0;
|
|
542
635
|
} else {
|
|
636
|
+
assert(obs == .continuing);
|
|
543
637
|
assert(n >= 1 and n < 5);
|
|
544
638
|
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
545
|
-
|
|
639
|
+
d.confusion += 1;
|
|
546
640
|
}
|
|
547
641
|
}
|
|
548
642
|
|
|
549
|
-
pub fn
|
|
643
|
+
pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
550
644
|
if (!enabled) return;
|
|
551
645
|
|
|
552
|
-
var
|
|
553
|
-
|
|
554
|
-
|
|
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;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
666
|
+
if (!enabled) return;
|
|
667
|
+
|
|
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
|
|
555
676
|
assert(n >= 2 and n <= 3);
|
|
556
677
|
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
557
|
-
|
|
678
|
+
d.attacking = 0;
|
|
558
679
|
} else {
|
|
680
|
+
assert(obs == .continuing);
|
|
559
681
|
assert(n >= 1 and n < 3);
|
|
560
682
|
if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
|
|
561
|
-
|
|
683
|
+
d.attacking += 1;
|
|
562
684
|
}
|
|
563
685
|
}
|
|
564
686
|
|
|
565
|
-
pub fn binding(self: *Self, player: Player,
|
|
687
|
+
pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
566
688
|
if (!enabled) return;
|
|
567
689
|
|
|
568
|
-
var
|
|
569
|
-
|
|
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;
|
|
570
696
|
|
|
571
697
|
assert(n > 0);
|
|
572
698
|
const p: u4 = if (n < 3) 3 else 1;
|
|
573
699
|
const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
|
|
574
700
|
|
|
575
|
-
if (
|
|
701
|
+
if (obs == .ended) {
|
|
702
|
+
// "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
576
703
|
assert(n >= 1 and n <= 4);
|
|
577
704
|
if (n != 4) try self.probability.update(p, q);
|
|
578
|
-
|
|
705
|
+
d.binding = 0;
|
|
579
706
|
} else {
|
|
707
|
+
assert(obs == .continuing);
|
|
580
708
|
assert(n >= 1 and n < 4);
|
|
581
709
|
try self.probability.update(q - p, q);
|
|
582
|
-
|
|
710
|
+
d.binding += 1;
|
|
583
711
|
}
|
|
584
712
|
}
|
|
585
713
|
|
|
@@ -593,9 +721,18 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
593
721
|
pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
|
|
594
722
|
if (!enabled) return;
|
|
595
723
|
|
|
596
|
-
try self.probability.update(1, Move.
|
|
724
|
+
try self.probability.update(1, Move.METRONOME.len);
|
|
597
725
|
self.actions.get(player).metronome = move;
|
|
598
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
|
+
}
|
|
599
736
|
};
|
|
600
737
|
}
|
|
601
738
|
|
|
@@ -618,19 +755,25 @@ test "Chance.speedTie" {
|
|
|
618
755
|
test "Chance.hit" {
|
|
619
756
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
620
757
|
|
|
621
|
-
chance.hit(true, 229);
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
+
}
|
|
625
765
|
try expectValue(Optional(bool).true, chance.actions.p1.hit);
|
|
626
766
|
try expectProbability(&chance.probability, 229, 256);
|
|
627
767
|
|
|
628
768
|
chance.reset();
|
|
629
769
|
|
|
630
|
-
chance.hit(false, 255);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
+
}
|
|
634
777
|
try expectValue(Optional(bool).false, chance.actions.p2.hit);
|
|
635
778
|
try expectProbability(&chance.probability, 1, 256);
|
|
636
779
|
}
|
|
@@ -646,26 +789,18 @@ test "Chance.criticalHit" {
|
|
|
646
789
|
try expectProbability(&chance.probability, 1, 1);
|
|
647
790
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
648
791
|
|
|
649
|
-
try chance.
|
|
650
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
651
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
652
|
-
|
|
653
|
-
chance.hit(true, 128);
|
|
792
|
+
try chance.hit(.P1, true, 128);
|
|
654
793
|
|
|
655
|
-
try chance.
|
|
656
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
794
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
657
795
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
658
796
|
|
|
659
|
-
chance.probability.reset();
|
|
660
797
|
try chance.damage(.P1, 217);
|
|
661
798
|
|
|
662
|
-
try chance.
|
|
663
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
799
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
664
800
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
665
801
|
|
|
666
|
-
chance.probability.reset();
|
|
667
|
-
|
|
668
802
|
try chance.commit(.P1, .hit);
|
|
803
|
+
|
|
669
804
|
try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
|
|
670
805
|
try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
|
|
671
806
|
}
|
|
@@ -680,20 +815,14 @@ test "Chance.criticalHit" {
|
|
|
680
815
|
try expectProbability(&chance.probability, 1, 1);
|
|
681
816
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
682
817
|
|
|
683
|
-
try chance.
|
|
684
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
685
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
818
|
+
try chance.hit(.P1, true, 128);
|
|
686
819
|
|
|
687
|
-
chance.
|
|
688
|
-
|
|
689
|
-
try chance.commit(.P1, .hit);
|
|
690
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
820
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
691
821
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
692
822
|
|
|
693
|
-
chance.probability.reset();
|
|
694
823
|
try chance.damage(.P1, 217);
|
|
695
|
-
|
|
696
824
|
try chance.commit(.P1, .hit);
|
|
825
|
+
|
|
697
826
|
try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
|
|
698
827
|
try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
|
|
699
828
|
}
|
|
@@ -710,11 +839,8 @@ test "Chance.damage" {
|
|
|
710
839
|
try expectProbability(&chance.probability, 1, 1);
|
|
711
840
|
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
712
841
|
|
|
713
|
-
try chance.commit(.P1, .miss);
|
|
714
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
715
|
-
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
716
|
-
|
|
717
842
|
try chance.commit(.P1, .hit);
|
|
843
|
+
|
|
718
844
|
try expectProbability(&chance.probability, 1, 39);
|
|
719
845
|
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
720
846
|
}
|
|
@@ -794,84 +920,77 @@ test "Chance.multiHit" {
|
|
|
794
920
|
test "Chance.duration" {
|
|
795
921
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
796
922
|
|
|
797
|
-
chance.duration(.
|
|
923
|
+
chance.duration(.P1, 2);
|
|
798
924
|
try expectValue(@as(u4, 2), chance.actions.p1.duration);
|
|
799
|
-
try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
|
|
800
925
|
|
|
801
926
|
chance.reset();
|
|
802
927
|
|
|
803
|
-
chance.duration(.
|
|
804
|
-
if (!showdown) {
|
|
805
|
-
try expectValue(@as(u4, 0), chance.actions.p2.duration);
|
|
806
|
-
try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
|
|
807
|
-
try chance.commit(.P2, .hit);
|
|
808
|
-
}
|
|
928
|
+
chance.duration(.P2, 4);
|
|
809
929
|
try expectValue(@as(u4, 4), chance.actions.p2.duration);
|
|
810
|
-
try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
|
|
811
930
|
}
|
|
812
931
|
|
|
813
932
|
test "Chance.sleep" {
|
|
814
933
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
815
934
|
|
|
816
935
|
for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
|
|
817
|
-
chance.
|
|
818
|
-
try chance.sleep(.P1,
|
|
936
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
937
|
+
try chance.sleep(.P1, .ended);
|
|
819
938
|
try expectProbability(&chance.probability, 1, d);
|
|
820
|
-
try expectValue(@as(u3, 0), chance.
|
|
939
|
+
try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
821
940
|
|
|
822
941
|
chance.reset();
|
|
823
942
|
|
|
824
943
|
if (i < 7) {
|
|
825
|
-
chance.
|
|
826
|
-
try chance.sleep(.P1,
|
|
944
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
945
|
+
try chance.sleep(.P1, .continuing);
|
|
827
946
|
try expectProbability(&chance.probability, d - 1, d);
|
|
828
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
947
|
+
try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
829
948
|
|
|
830
949
|
chance.reset();
|
|
831
950
|
}
|
|
832
951
|
}
|
|
833
952
|
}
|
|
834
953
|
|
|
835
|
-
test "Chance.
|
|
954
|
+
test "Chance.confusion" {
|
|
836
955
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
837
956
|
|
|
838
|
-
for ([_]u8{
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
957
|
+
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
958
|
+
if (i > 1) {
|
|
959
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
960
|
+
try chance.confusion(.P2, .ended);
|
|
961
|
+
try expectProbability(&chance.probability, 1, d);
|
|
962
|
+
try expectValue(@as(u3, 0), chance.durations.p2.confusion);
|
|
843
963
|
|
|
844
|
-
|
|
964
|
+
chance.reset();
|
|
965
|
+
}
|
|
845
966
|
|
|
846
|
-
if (i <
|
|
847
|
-
chance.
|
|
848
|
-
try chance.
|
|
849
|
-
try expectProbability(&chance.probability, d - 1, d);
|
|
850
|
-
try expectValue(@as(
|
|
967
|
+
if (i < 5) {
|
|
968
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
969
|
+
try chance.confusion(.P2, .continuing);
|
|
970
|
+
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
971
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
|
|
851
972
|
|
|
852
973
|
chance.reset();
|
|
853
974
|
}
|
|
854
975
|
}
|
|
855
976
|
}
|
|
856
977
|
|
|
857
|
-
test "Chance.
|
|
978
|
+
test "Chance.disable" {
|
|
858
979
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
859
980
|
|
|
860
|
-
for ([_]u8{
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
|
|
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);
|
|
866
986
|
|
|
867
|
-
|
|
868
|
-
}
|
|
987
|
+
chance.reset();
|
|
869
988
|
|
|
870
|
-
if (i <
|
|
871
|
-
chance.
|
|
872
|
-
try chance.
|
|
873
|
-
try expectProbability(&chance.probability,
|
|
874
|
-
try expectValue(@as(
|
|
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);
|
|
875
994
|
|
|
876
995
|
chance.reset();
|
|
877
996
|
}
|
|
@@ -883,19 +1002,19 @@ test "Chance.attacking" {
|
|
|
883
1002
|
|
|
884
1003
|
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
885
1004
|
if (i > 1) {
|
|
886
|
-
chance.
|
|
887
|
-
try chance.attacking(.P2,
|
|
1005
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1006
|
+
try chance.attacking(.P2, .ended);
|
|
888
1007
|
try expectProbability(&chance.probability, 1, d);
|
|
889
|
-
try expectValue(@as(u3, 0), chance.
|
|
1008
|
+
try expectValue(@as(u3, 0), chance.durations.p2.attacking);
|
|
890
1009
|
|
|
891
1010
|
chance.reset();
|
|
892
1011
|
}
|
|
893
1012
|
|
|
894
1013
|
if (i < 3) {
|
|
895
|
-
chance.
|
|
896
|
-
try chance.attacking(.P2,
|
|
1014
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1015
|
+
try chance.attacking(.P2, .continuing);
|
|
897
1016
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
898
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1017
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
|
|
899
1018
|
|
|
900
1019
|
chance.reset();
|
|
901
1020
|
}
|
|
@@ -909,18 +1028,18 @@ test "Chance.binding" {
|
|
|
909
1028
|
const qs = [_]u8{ 8, 5, 2, 1 };
|
|
910
1029
|
|
|
911
1030
|
for (ps, qs, 1..5) |p, q, i| {
|
|
912
|
-
chance.
|
|
913
|
-
try chance.binding(.P1,
|
|
1031
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1032
|
+
try chance.binding(.P1, .ended);
|
|
914
1033
|
try expectProbability(&chance.probability, p, q);
|
|
915
|
-
try expectValue(@as(u3, 0), chance.
|
|
1034
|
+
try expectValue(@as(u3, 0), chance.durations.p1.binding);
|
|
916
1035
|
|
|
917
1036
|
chance.reset();
|
|
918
1037
|
|
|
919
1038
|
if (i < 4) {
|
|
920
|
-
chance.
|
|
921
|
-
try chance.binding(.P1,
|
|
1039
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1040
|
+
try chance.binding(.P1, .continuing);
|
|
922
1041
|
try expectProbability(&chance.probability, q - p, q);
|
|
923
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1042
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
|
|
924
1043
|
|
|
925
1044
|
chance.reset();
|
|
926
1045
|
}
|
|
@@ -970,24 +1089,24 @@ const Null = struct {
|
|
|
970
1089
|
_ = .{ self, player, kind };
|
|
971
1090
|
}
|
|
972
1091
|
|
|
973
|
-
pub fn
|
|
1092
|
+
pub fn glitch(self: Null) void {
|
|
974
1093
|
_ = .{self};
|
|
975
1094
|
}
|
|
976
1095
|
|
|
977
|
-
pub fn
|
|
978
|
-
_ = .{
|
|
1096
|
+
pub fn clearPending(self: Null) void {
|
|
1097
|
+
_ = .{self};
|
|
979
1098
|
}
|
|
980
1099
|
|
|
981
|
-
pub fn switched(self: Null, player: Player,
|
|
982
|
-
_ = .{ self, player,
|
|
1100
|
+
pub fn switched(self: Null, player: Player, slot: u8) void {
|
|
1101
|
+
_ = .{ self, player, slot };
|
|
983
1102
|
}
|
|
984
1103
|
|
|
985
1104
|
pub fn speedTie(self: Null, p1: bool) Error!void {
|
|
986
1105
|
_ = .{ self, p1 };
|
|
987
1106
|
}
|
|
988
1107
|
|
|
989
|
-
pub fn hit(self: Null, ok: bool, accuracy:
|
|
990
|
-
_ = .{ self, ok, accuracy };
|
|
1108
|
+
pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
1109
|
+
_ = .{ self, player, ok, accuracy };
|
|
991
1110
|
}
|
|
992
1111
|
|
|
993
1112
|
pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -998,10 +1117,6 @@ const Null = struct {
|
|
|
998
1117
|
_ = .{ self, player, roll };
|
|
999
1118
|
}
|
|
1000
1119
|
|
|
1001
|
-
pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
|
|
1002
|
-
_ = .{ self, player, proc, rate };
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
1120
|
pub fn confused(self: Null, player: Player, ok: bool) Error!void {
|
|
1006
1121
|
_ = .{ self, player, ok };
|
|
1007
1122
|
}
|
|
@@ -1010,6 +1125,10 @@ const Null = struct {
|
|
|
1010
1125
|
_ = .{ self, player, ok };
|
|
1011
1126
|
}
|
|
1012
1127
|
|
|
1128
|
+
pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
|
|
1129
|
+
_ = .{ self, player, proc, rate };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1013
1132
|
pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
|
|
1014
1133
|
_ = .{ self, player, slot, ms, n };
|
|
1015
1134
|
}
|
|
@@ -1018,34 +1137,37 @@ const Null = struct {
|
|
|
1018
1137
|
_ = .{ self, player, n };
|
|
1019
1138
|
}
|
|
1020
1139
|
|
|
1021
|
-
pub fn duration(
|
|
1140
|
+
pub fn duration(self: Null, player: Player, turns: u4) void {
|
|
1141
|
+
_ = .{ self, player, turns };
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
pub fn observe(
|
|
1022
1145
|
self: Null,
|
|
1023
|
-
comptime field:
|
|
1146
|
+
comptime field: Action.Field,
|
|
1024
1147
|
player: Player,
|
|
1025
|
-
|
|
1026
|
-
turns: u4,
|
|
1148
|
+
obs: Optional(Confusion),
|
|
1027
1149
|
) void {
|
|
1028
|
-
_ = .{ self, field, player,
|
|
1150
|
+
_ = .{ self, field, player, obs };
|
|
1029
1151
|
}
|
|
1030
1152
|
|
|
1031
|
-
pub fn sleep(self: Null, player: Player,
|
|
1032
|
-
_ = .{ self, player,
|
|
1153
|
+
pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1154
|
+
_ = .{ self, player, obs };
|
|
1033
1155
|
}
|
|
1034
1156
|
|
|
1035
|
-
pub fn
|
|
1036
|
-
_ = .{ self, player,
|
|
1157
|
+
pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
|
|
1158
|
+
_ = .{ self, player, obs };
|
|
1037
1159
|
}
|
|
1038
1160
|
|
|
1039
|
-
pub fn
|
|
1040
|
-
_ = .{ self, player,
|
|
1161
|
+
pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1162
|
+
_ = .{ self, player, obs };
|
|
1041
1163
|
}
|
|
1042
1164
|
|
|
1043
|
-
pub fn attacking(self: Null, player: Player,
|
|
1044
|
-
_ = .{ self, player,
|
|
1165
|
+
pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1166
|
+
_ = .{ self, player, obs };
|
|
1045
1167
|
}
|
|
1046
1168
|
|
|
1047
|
-
pub fn binding(self: Null, player: Player,
|
|
1048
|
-
_ = .{ self, player,
|
|
1169
|
+
pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1170
|
+
_ = .{ self, player, obs };
|
|
1049
1171
|
}
|
|
1050
1172
|
|
|
1051
1173
|
pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
|
|
@@ -1055,4 +1177,9 @@ const Null = struct {
|
|
|
1055
1177
|
pub fn metronome(self: Null, player: Player, move: Move) Error!void {
|
|
1056
1178
|
_ = .{ self, player, move };
|
|
1057
1179
|
}
|
|
1180
|
+
|
|
1181
|
+
pub fn pp(self: Null, player: Player, n: u4) bool {
|
|
1182
|
+
_ = .{ self, player, n };
|
|
1183
|
+
return true;
|
|
1184
|
+
}
|
|
1058
1185
|
};
|