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