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