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