@pkmn/engine 0.1.0-dev.b01cb48c → 0.1.0-dev.b14f2221
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 +21 -11
- 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 +255 -102
- 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 +30 -9
- package/src/lib/common/js.zig +3 -7
- package/src/lib/common/optional.zig +11 -17
- package/src/lib/common/options.zig +4 -0
- package/src/lib/common/protocol.zig +195 -159
- package/src/lib/common/rational.zig +303 -310
- package/src/lib/common/rng.zig +5 -7
- package/src/lib/common/util.zig +33 -1
- package/src/lib/gen1/calc.zig +324 -180
- package/src/lib/gen1/chance.zig +536 -140
- 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 +40 -43
- package/src/lib/gen1/helpers.zig +17 -29
- package/src/lib/gen1/mechanics.zig +240 -187
- package/src/lib/gen2/calc.zig +35 -28
- package/src/lib/gen2/chance.zig +88 -74
- 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 +9 -20
- package/src/lib/gen2/mechanics.zig +98 -98
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +4 -121
- package/src/lib/pkmn.zig +103 -22
- 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 -405
- 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,8 +38,8 @@ 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
|
|
|
@@ -51,15 +52,15 @@ pub const Actions = extern struct {
|
|
|
51
52
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
52
53
|
/// set to the same value).
|
|
53
54
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
54
|
-
inline for (@typeInfo(Actions).
|
|
55
|
-
inline for (@typeInfo(Action).
|
|
55
|
+
inline for (@typeInfo(Actions).@"struct".fields) |player| {
|
|
56
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
56
57
|
const a_val = @field(@field(a, player.name), field.name);
|
|
57
58
|
const b_val = @field(@field(b, player.name), field.name);
|
|
58
59
|
|
|
59
60
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
60
|
-
|
|
61
|
+
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
61
62
|
return false,
|
|
62
|
-
.
|
|
63
|
+
.int => if ((a_val > 0) != (b_val > 0)) return false,
|
|
63
64
|
else => unreachable,
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -67,7 +68,26 @@ pub const Actions = extern struct {
|
|
|
67
68
|
return true;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
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 {
|
|
71
91
|
try writer.writeAll("<P1 = ");
|
|
72
92
|
try self.p1.fmt(writer, shape);
|
|
73
93
|
try writer.writeAll(", P2 = ");
|
|
@@ -75,8 +95,7 @@ pub const Actions = extern struct {
|
|
|
75
95
|
try writer.writeAll(">");
|
|
76
96
|
}
|
|
77
97
|
|
|
78
|
-
pub fn format(a: Actions,
|
|
79
|
-
_ = .{ f, o };
|
|
98
|
+
pub fn format(a: Actions, w: *std.Io.Writer) !void {
|
|
80
99
|
try fmt(a, w, false);
|
|
81
100
|
}
|
|
82
101
|
};
|
|
@@ -99,8 +118,42 @@ test Actions {
|
|
|
99
118
|
try expect(!c.matches(a));
|
|
100
119
|
}
|
|
101
120
|
|
|
102
|
-
|
|
103
|
-
pub
|
|
121
|
+
/// Observation made about a duration - whether the duration has started, been continued, or ended.
|
|
122
|
+
pub fn Observation(comptime field: Duration.Field) type {
|
|
123
|
+
return switch (field) {
|
|
124
|
+
.attacking, .binding => enum(u2) {
|
|
125
|
+
started,
|
|
126
|
+
continuing,
|
|
127
|
+
ended,
|
|
128
|
+
},
|
|
129
|
+
.sleep, .disable => enum(u3) {
|
|
130
|
+
started,
|
|
131
|
+
continuing,
|
|
132
|
+
ended,
|
|
133
|
+
// TODO haze support
|
|
134
|
+
unused1,
|
|
135
|
+
unused2,
|
|
136
|
+
unused3,
|
|
137
|
+
unused4,
|
|
138
|
+
},
|
|
139
|
+
.confusion => enum(u4) {
|
|
140
|
+
started,
|
|
141
|
+
continuing,
|
|
142
|
+
ended,
|
|
143
|
+
/// An indicator that an existing confusion duration has been overwritten by a new
|
|
144
|
+
/// confusion duration due to a Thrashing move ending ("hidden" confusion). This is not
|
|
145
|
+
/// immediately observable to an opponent, to compute probabilities from an opponent's
|
|
146
|
+
/// point of view based solely on public information extra work must be done to correct
|
|
147
|
+
/// for the information leak
|
|
148
|
+
overwritten,
|
|
149
|
+
// TODO haze support
|
|
150
|
+
unused1,
|
|
151
|
+
unused2,
|
|
152
|
+
unused3,
|
|
153
|
+
unused4,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
104
157
|
|
|
105
158
|
/// Information about the RNG that was observed during a Generation I battle `update` for a
|
|
106
159
|
/// single player.
|
|
@@ -126,21 +179,25 @@ pub const Action = packed struct(u64) {
|
|
|
126
179
|
/// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
|
|
127
180
|
duration: u4 = 0,
|
|
128
181
|
|
|
129
|
-
|
|
130
|
-
sleep: Optional(Observation) = .None,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
/// TODO
|
|
183
|
+
sleep: Optional(Observation(.sleep)) = .None,
|
|
184
|
+
/// TODO
|
|
185
|
+
confusion: Optional(Observation(.confusion)) = .None,
|
|
186
|
+
/// TODO
|
|
187
|
+
disable: Optional(Observation(.disable)) = .None,
|
|
188
|
+
/// TODO
|
|
189
|
+
attacking: Optional(Observation(.attacking)) = .None,
|
|
190
|
+
/// TODO
|
|
191
|
+
binding: Optional(Observation(.binding)) = .None,
|
|
138
192
|
|
|
139
193
|
/// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
|
|
140
194
|
/// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
|
|
141
|
-
move_slot:
|
|
195
|
+
move_slot: u3 = 0,
|
|
142
196
|
/// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
|
|
143
|
-
multi_hit:
|
|
197
|
+
multi_hit: u3 = 0,
|
|
198
|
+
|
|
199
|
+
/// TODO
|
|
200
|
+
pp: u4 = 0,
|
|
144
201
|
|
|
145
202
|
/// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
|
|
146
203
|
psywave: u8 = 0,
|
|
@@ -150,18 +207,19 @@ pub const Action = packed struct(u64) {
|
|
|
150
207
|
|
|
151
208
|
pub const Field = std.meta.FieldEnum(Action);
|
|
152
209
|
|
|
153
|
-
pub fn format(a: Action,
|
|
154
|
-
_ = .{ f, o };
|
|
210
|
+
pub fn format(a: Action, w: *std.Io.Writer) !void {
|
|
155
211
|
try fmt(a, w, false);
|
|
156
212
|
}
|
|
157
213
|
|
|
158
|
-
|
|
214
|
+
const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
|
|
215
|
+
|
|
216
|
+
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
159
217
|
try writer.writeByte('(');
|
|
160
218
|
var printed = false;
|
|
161
|
-
inline for (@typeInfo(Action).
|
|
219
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
162
220
|
const val = @field(self, field.name);
|
|
163
221
|
switch (@typeInfo(@TypeOf(val))) {
|
|
164
|
-
|
|
222
|
+
.@"enum" => if (val != .None) {
|
|
165
223
|
if (printed) try writer.writeAll(", ");
|
|
166
224
|
if (shape) {
|
|
167
225
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -170,33 +228,14 @@ pub const Action = packed struct(u64) {
|
|
|
170
228
|
if (val == .false) "!" else "",
|
|
171
229
|
field.name,
|
|
172
230
|
});
|
|
173
|
-
} else if (@TypeOf(val)
|
|
174
|
-
try writer.print("{s}{s}", .{
|
|
175
|
-
switch (val) {
|
|
176
|
-
.started => "+",
|
|
177
|
-
.continuing => "",
|
|
178
|
-
.ended => "-",
|
|
179
|
-
else => unreachable,
|
|
180
|
-
},
|
|
181
|
-
field.name,
|
|
182
|
-
});
|
|
183
|
-
} else if (@TypeOf(val) == Optional(Confusion)) {
|
|
184
|
-
try writer.print("{s}{s}", .{
|
|
185
|
-
switch (val) {
|
|
186
|
-
.started => "+",
|
|
187
|
-
.continuing => "",
|
|
188
|
-
.ended => "-",
|
|
189
|
-
.overwritten => "#",
|
|
190
|
-
else => unreachable,
|
|
191
|
-
},
|
|
192
|
-
field.name,
|
|
193
|
-
});
|
|
231
|
+
} else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
|
|
232
|
+
try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field.name });
|
|
194
233
|
} else {
|
|
195
234
|
try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
|
|
196
235
|
}
|
|
197
236
|
printed = true;
|
|
198
237
|
},
|
|
199
|
-
.
|
|
238
|
+
.int => if (val != 0) {
|
|
200
239
|
if (printed) try writer.writeAll(", ");
|
|
201
240
|
if (shape) {
|
|
202
241
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -213,68 +252,63 @@ pub const Action = packed struct(u64) {
|
|
|
213
252
|
};
|
|
214
253
|
|
|
215
254
|
/// TODO
|
|
216
|
-
pub const Durations = struct {
|
|
255
|
+
pub const Durations = extern struct {
|
|
217
256
|
/// TODO
|
|
218
257
|
p1: Duration = .{},
|
|
219
258
|
/// TODO
|
|
220
259
|
p2: Duration = .{},
|
|
221
260
|
|
|
261
|
+
comptime {
|
|
262
|
+
assert(@sizeOf(Durations) == 8);
|
|
263
|
+
}
|
|
264
|
+
|
|
222
265
|
/// TODO
|
|
223
|
-
pub fn get(self: anytype, player: Player)
|
|
224
|
-
assert(
|
|
266
|
+
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
|
|
267
|
+
assert(isPointerTo(self, Durations));
|
|
225
268
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
226
269
|
}
|
|
227
270
|
|
|
228
|
-
pub fn format(
|
|
229
|
-
self
|
|
230
|
-
comptime fmt: []const u8,
|
|
231
|
-
opts: std.fmt.FormatOptions,
|
|
232
|
-
writer: anytype,
|
|
233
|
-
) !void {
|
|
234
|
-
_ = .{ fmt, opts };
|
|
235
|
-
try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
|
|
271
|
+
pub fn format(self: Durations, writer: *std.Io.Writer) !void {
|
|
272
|
+
try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
|
|
236
273
|
}
|
|
237
274
|
};
|
|
238
275
|
|
|
276
|
+
pub const Sleeps = Array(6, u3);
|
|
277
|
+
|
|
239
278
|
/// TODO
|
|
240
|
-
pub const Duration = struct {
|
|
241
|
-
sleeps:
|
|
279
|
+
pub const Duration = packed struct(u32) {
|
|
280
|
+
sleeps: Sleeps.T = 0,
|
|
242
281
|
confusion: u3 = 0,
|
|
243
282
|
disable: u4 = 0,
|
|
244
|
-
|
|
245
|
-
thrashing: u3 = 0,
|
|
283
|
+
attacking: u3 = 0,
|
|
246
284
|
binding: u3 = 0,
|
|
247
285
|
|
|
286
|
+
_: u1 = 0,
|
|
287
|
+
|
|
288
|
+
pub const Field = enum { attacking, binding, sleep, disable, confusion };
|
|
289
|
+
|
|
248
290
|
pub fn format(
|
|
249
291
|
self: Duration,
|
|
250
|
-
|
|
251
|
-
opts: std.fmt.FormatOptions,
|
|
252
|
-
writer: anytype,
|
|
292
|
+
writer: *std.Io.Writer,
|
|
253
293
|
) !void {
|
|
254
|
-
_ = .{ fmt, opts };
|
|
255
294
|
try writer.writeByte('(');
|
|
256
295
|
var printed = false;
|
|
257
|
-
inline for (@typeInfo(Duration).
|
|
296
|
+
inline for (@typeInfo(Duration).@"struct".fields) |field| {
|
|
258
297
|
const val = @field(self, field.name);
|
|
259
298
|
switch (@typeInfo(@TypeOf(val))) {
|
|
260
|
-
.
|
|
299
|
+
.int => if (val != 0) {
|
|
261
300
|
if (printed) try writer.writeAll(", ");
|
|
262
|
-
|
|
263
|
-
printed = true;
|
|
264
|
-
},
|
|
265
|
-
.Array => {
|
|
266
|
-
var total: usize = 0;
|
|
267
|
-
for (val) |v| total += v;
|
|
268
|
-
if (total > 0) {
|
|
269
|
-
if (printed) try writer.writeAll(", ");
|
|
301
|
+
if (comptime std.mem.eql(u8, field.name, "sleeps")) {
|
|
270
302
|
try writer.print("{s}:[", .{field.name});
|
|
271
|
-
for (
|
|
303
|
+
for (0..6) |i| {
|
|
272
304
|
if (i != 0) try writer.writeByte(',');
|
|
273
|
-
try writer.print("{d}", .{
|
|
305
|
+
try writer.print("{d}", .{Sleeps.get(val, i)});
|
|
274
306
|
}
|
|
275
307
|
try writer.writeAll("]");
|
|
276
|
-
|
|
308
|
+
} else {
|
|
309
|
+
try writer.print("{s}:{d}", .{ field.name, val });
|
|
277
310
|
}
|
|
311
|
+
printed = true;
|
|
278
312
|
},
|
|
279
313
|
else => unreachable,
|
|
280
314
|
}
|
|
@@ -283,7 +317,7 @@ pub const Duration = struct {
|
|
|
283
317
|
}
|
|
284
318
|
};
|
|
285
319
|
|
|
286
|
-
pub const Commit = enum { hit, miss, binding };
|
|
320
|
+
pub const Commit = enum { hit, miss, binding, err, glitch };
|
|
287
321
|
|
|
288
322
|
const uN = if (options.miss) u8 else u9;
|
|
289
323
|
|
|
@@ -323,6 +357,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
323
357
|
damage_roll: u8 = 0,
|
|
324
358
|
hit: bool = false,
|
|
325
359
|
hit_probablity: uN = 0,
|
|
360
|
+
glitch: bool = false,
|
|
326
361
|
} = .{},
|
|
327
362
|
|
|
328
363
|
/// Possible error returned by operations tracking chance probability.
|
|
@@ -342,10 +377,18 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
342
377
|
|
|
343
378
|
assert(!showdown);
|
|
344
379
|
|
|
380
|
+
if (kind == .glitch and !self.pending.glitch) return;
|
|
381
|
+
|
|
345
382
|
var action = self.actions.get(player);
|
|
346
383
|
|
|
384
|
+
if (kind == .err and self.pending.crit_probablity != 0) {
|
|
385
|
+
try self.probability.update(self.pending.crit_probablity, 256);
|
|
386
|
+
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
347
390
|
// Always commit the hit result if we make it here (commit won't be called at all if the
|
|
348
|
-
// target is immune
|
|
391
|
+
// target is immune*/behind a sub/etc)
|
|
349
392
|
if (self.pending.hit_probablity != 0) {
|
|
350
393
|
try self.probability.update(self.pending.hit_probablity, 256);
|
|
351
394
|
action.hit = if (self.pending.hit) .true else .false;
|
|
@@ -353,28 +396,61 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
353
396
|
|
|
354
397
|
// If the move actually lands we can commit any past critical hit / damage rolls. Note
|
|
355
398
|
// we can't rely on the presence or absence of a damage roll to determine whether a
|
|
356
|
-
// "critical hit" was spurious or not as moves that do 1 or less damage don't
|
|
399
|
+
// "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
|
|
357
400
|
// damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
|
|
358
|
-
// passed to the critical hit helper)
|
|
359
|
-
|
|
401
|
+
// passed to the critical hit helper).
|
|
402
|
+
//
|
|
403
|
+
// There is a special edge case with the division-by-zero freeze where we need to commit
|
|
404
|
+
// the status of critical hit roll the occured before the move missed to avoid missing
|
|
405
|
+
// out on the possibility that the alternative branch encountered an error
|
|
406
|
+
if (kind != .hit and !self.pending.glitch) {
|
|
407
|
+
return assert(!self.pending.crit or self.pending.crit_probablity > 0);
|
|
408
|
+
}
|
|
360
409
|
|
|
361
410
|
if (self.pending.crit_probablity != 0) {
|
|
362
411
|
try self.probability.update(self.pending.crit_probablity, 256);
|
|
363
412
|
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
364
|
-
}
|
|
413
|
+
} else assert(!self.pending.glitch);
|
|
365
414
|
|
|
366
|
-
if (self.pending.damage_roll > 0) {
|
|
415
|
+
if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
|
|
367
416
|
try self.probability.update(1, 39);
|
|
368
417
|
action.damage = self.pending.damage_roll;
|
|
369
418
|
}
|
|
370
419
|
}
|
|
371
420
|
|
|
421
|
+
pub fn glitch(self: *Self) void {
|
|
422
|
+
if (!enabled) return;
|
|
423
|
+
|
|
424
|
+
assert(!showdown);
|
|
425
|
+
self.pending.glitch = true;
|
|
426
|
+
}
|
|
427
|
+
|
|
372
428
|
pub fn clearPending(self: *Self) void {
|
|
373
429
|
if (!enabled) return;
|
|
374
430
|
|
|
431
|
+
assert(!showdown);
|
|
375
432
|
self.pending = .{};
|
|
376
433
|
}
|
|
377
434
|
|
|
435
|
+
pub fn switched(self: *Self, player: Player, slot: u8) void {
|
|
436
|
+
if (!enabled) return;
|
|
437
|
+
|
|
438
|
+
assert(slot >= 1 and slot <= 6);
|
|
439
|
+
|
|
440
|
+
var d = self.durations.get(player);
|
|
441
|
+
|
|
442
|
+
const out = Sleeps.get(d.sleeps, 0);
|
|
443
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
|
|
444
|
+
d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
|
|
445
|
+
|
|
446
|
+
d.confusion = 0;
|
|
447
|
+
d.disable = 0;
|
|
448
|
+
d.attacking = 0;
|
|
449
|
+
d.binding = 0;
|
|
450
|
+
|
|
451
|
+
self.durations.get(player.foe()).binding = 0;
|
|
452
|
+
}
|
|
453
|
+
|
|
378
454
|
pub fn speedTie(self: *Self, p1: bool) Error!void {
|
|
379
455
|
if (!enabled) return;
|
|
380
456
|
|
|
@@ -474,36 +550,185 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
474
550
|
pub fn duration(self: *Self, player: Player, turns: u4) void {
|
|
475
551
|
if (!enabled) return;
|
|
476
552
|
|
|
477
|
-
self.actions.get(player).duration = if (options.key)
|
|
553
|
+
self.actions.get(player).duration = if (options.key) 2 else turns;
|
|
478
554
|
}
|
|
479
555
|
|
|
480
556
|
pub fn observe(
|
|
481
557
|
self: *Self,
|
|
482
558
|
comptime field: Action.Field,
|
|
483
559
|
player: Player,
|
|
484
|
-
|
|
560
|
+
obs: Optional(Observation(.confusion)),
|
|
485
561
|
) void {
|
|
486
562
|
if (!enabled) return;
|
|
487
563
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
564
|
+
var a = self.actions.get(player);
|
|
565
|
+
const val = @field(a, @tagName(field));
|
|
566
|
+
switch (obs) {
|
|
567
|
+
.overwritten => {
|
|
568
|
+
assert(field == .confusion);
|
|
569
|
+
assert(val == .started or val == .continuing);
|
|
570
|
+
assert(a.duration > 0);
|
|
571
|
+
a.confusion = obs;
|
|
572
|
+
self.durations.get(player).confusion = 1;
|
|
573
|
+
},
|
|
574
|
+
.None => {
|
|
575
|
+
@field(a, @tagName(field)) = .None;
|
|
576
|
+
if (field == .sleep) {
|
|
577
|
+
var d = self.durations.get(player);
|
|
578
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
579
|
+
} else {
|
|
580
|
+
@field(self.durations.get(player), @tagName(field)) = 0;
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
.started => {
|
|
584
|
+
assert(val == .None or val == .ended);
|
|
585
|
+
assert(switch (field) {
|
|
586
|
+
.confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
|
|
587
|
+
.sleep, .disable => self.actions.get(player.foe()).duration > 0,
|
|
588
|
+
else => a.duration > 0,
|
|
589
|
+
});
|
|
590
|
+
@field(a, @tagName(field)) = .started;
|
|
591
|
+
if (field == .sleep) {
|
|
592
|
+
var d = self.durations.get(player);
|
|
593
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 1);
|
|
594
|
+
} else {
|
|
595
|
+
@field(self.durations.get(player), @tagName(field)) = 1;
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
else => unreachable,
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
|
|
603
|
+
if (!enabled) return;
|
|
604
|
+
|
|
605
|
+
var a = self.actions.get(player);
|
|
606
|
+
assert(a.sleep == .None or a.sleep == .started);
|
|
607
|
+
a.sleep = obs;
|
|
608
|
+
|
|
609
|
+
var d = self.durations.get(player);
|
|
610
|
+
const n = Sleeps.get(d.sleeps, 0);
|
|
611
|
+
|
|
612
|
+
if (obs == .None) {
|
|
613
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
614
|
+
} else if (obs == .ended) {
|
|
615
|
+
assert(n >= 1 and n <= 7);
|
|
616
|
+
if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
|
|
617
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
494
618
|
} else {
|
|
495
|
-
assert(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
619
|
+
assert(obs == .continuing);
|
|
620
|
+
assert(n >= 1 and n < 7);
|
|
621
|
+
try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
|
|
622
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
pub fn confusion(
|
|
627
|
+
self: *Self,
|
|
628
|
+
player: Player,
|
|
629
|
+
obs: Optional(Observation(.confusion)),
|
|
630
|
+
) Error!void {
|
|
631
|
+
if (!enabled) return;
|
|
632
|
+
|
|
633
|
+
var a = self.actions.get(player);
|
|
634
|
+
assert(a.confusion == .None or a.confusion == .started);
|
|
635
|
+
a.confusion = obs;
|
|
636
|
+
|
|
637
|
+
var d = self.durations.get(player);
|
|
638
|
+
const n = d.confusion;
|
|
639
|
+
|
|
640
|
+
if (obs == .ended) {
|
|
641
|
+
assert(n >= 2 and n <= 5);
|
|
642
|
+
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
643
|
+
d.confusion = 0;
|
|
644
|
+
} else {
|
|
645
|
+
assert(obs == .continuing);
|
|
646
|
+
assert(n >= 1 and n < 5);
|
|
647
|
+
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
648
|
+
d.confusion += 1;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
pub fn disable(
|
|
653
|
+
self: *Self,
|
|
654
|
+
player: Player,
|
|
655
|
+
obs: Optional(Observation(.disable)),
|
|
656
|
+
) Error!void {
|
|
657
|
+
if (!enabled) return;
|
|
658
|
+
|
|
659
|
+
var a = self.actions.get(player);
|
|
660
|
+
assert(a.disable == .None or a.disable == .started);
|
|
661
|
+
a.disable = obs;
|
|
662
|
+
|
|
663
|
+
var d = self.durations.get(player);
|
|
664
|
+
const n = d.disable;
|
|
665
|
+
|
|
666
|
+
if (obs == .ended) {
|
|
667
|
+
assert(n >= 1 and n <= 8);
|
|
668
|
+
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
669
|
+
d.disable = 0;
|
|
670
|
+
} else {
|
|
671
|
+
assert(obs == .continuing);
|
|
672
|
+
assert(n >= 1 and n < 8);
|
|
673
|
+
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
674
|
+
d.disable += 1;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
pub fn attacking(
|
|
679
|
+
self: *Self,
|
|
680
|
+
player: Player,
|
|
681
|
+
obs: Optional(Observation(.attacking)),
|
|
682
|
+
) Error!void {
|
|
683
|
+
if (!enabled) return;
|
|
684
|
+
|
|
685
|
+
var a = self.actions.get(player);
|
|
686
|
+
assert(a.attacking == .None or a.attacking == .started);
|
|
687
|
+
a.attacking = obs;
|
|
688
|
+
|
|
689
|
+
var d = self.durations.get(player);
|
|
690
|
+
const n = d.attacking;
|
|
691
|
+
if (obs == .ended) {
|
|
692
|
+
// "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
693
|
+
assert(n >= 2 and n <= 3);
|
|
694
|
+
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
695
|
+
d.attacking = 0;
|
|
696
|
+
} else {
|
|
697
|
+
assert(obs == .continuing);
|
|
698
|
+
assert(n >= 1 and n < 3);
|
|
699
|
+
if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
|
|
700
|
+
d.attacking += 1;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
pub fn binding(
|
|
705
|
+
self: *Self,
|
|
706
|
+
player: Player,
|
|
707
|
+
obs: Optional(Observation(.binding)),
|
|
708
|
+
) Error!void {
|
|
709
|
+
if (!enabled) return;
|
|
710
|
+
|
|
711
|
+
var a = self.actions.get(player);
|
|
712
|
+
assert(a.binding == .None or a.binding == .started);
|
|
713
|
+
a.binding = obs;
|
|
714
|
+
|
|
715
|
+
var d = self.durations.get(player);
|
|
716
|
+
const n = d.binding;
|
|
717
|
+
|
|
718
|
+
assert(n > 0);
|
|
719
|
+
const p: u4 = if (n < 3) 3 else 1;
|
|
720
|
+
const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
|
|
721
|
+
|
|
722
|
+
if (obs == .ended) {
|
|
723
|
+
// "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
724
|
+
assert(n >= 1 and n <= 4);
|
|
725
|
+
if (n != 4) try self.probability.update(p, q);
|
|
726
|
+
d.binding = 0;
|
|
727
|
+
} else {
|
|
728
|
+
assert(obs == .continuing);
|
|
729
|
+
assert(n >= 1 and n < 4);
|
|
730
|
+
try self.probability.update(q - p, q);
|
|
731
|
+
d.binding += 1;
|
|
507
732
|
}
|
|
508
733
|
}
|
|
509
734
|
|
|
@@ -520,6 +745,15 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
520
745
|
try self.probability.update(1, Move.METRONOME.len);
|
|
521
746
|
self.actions.get(player).metronome = move;
|
|
522
747
|
}
|
|
748
|
+
|
|
749
|
+
pub fn pp(self: *Self, player: Player, n: u4) bool {
|
|
750
|
+
if (!enabled) return true;
|
|
751
|
+
|
|
752
|
+
var a = self.actions.get(player);
|
|
753
|
+
if (n > 1 and a.pp == n) return false;
|
|
754
|
+
if (a.metronome != .None) a.pp += n;
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
523
757
|
};
|
|
524
758
|
}
|
|
525
759
|
|
|
@@ -620,16 +854,16 @@ test "Chance.damage" {
|
|
|
620
854
|
|
|
621
855
|
try chance.damage(.P1, 217);
|
|
622
856
|
if (showdown) {
|
|
623
|
-
try expectValue(217, chance.actions.p1.damage);
|
|
857
|
+
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
624
858
|
try expectProbability(&chance.probability, 1, 39);
|
|
625
859
|
} else {
|
|
626
860
|
try expectProbability(&chance.probability, 1, 1);
|
|
627
|
-
try expectValue(0, chance.actions.p1.damage);
|
|
861
|
+
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
628
862
|
|
|
629
863
|
try chance.commit(.P1, .hit);
|
|
630
864
|
|
|
631
865
|
try expectProbability(&chance.probability, 1, 39);
|
|
632
|
-
try expectValue(217, chance.actions.p1.damage);
|
|
866
|
+
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
633
867
|
}
|
|
634
868
|
}
|
|
635
869
|
|
|
@@ -681,13 +915,13 @@ test "Chance.moveSlot" {
|
|
|
681
915
|
|
|
682
916
|
try chance.moveSlot(.P2, 2, &ms, 2);
|
|
683
917
|
try expectProbability(&chance.probability, 1, 2);
|
|
684
|
-
try expectValue(2, chance.actions.p2.move_slot);
|
|
918
|
+
try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
|
|
685
919
|
|
|
686
920
|
chance.reset();
|
|
687
921
|
|
|
688
922
|
try chance.moveSlot(.P1, 1, &ms, 0);
|
|
689
923
|
try expectProbability(&chance.probability, 1, 3);
|
|
690
|
-
try expectValue(1, chance.actions.p1.move_slot);
|
|
924
|
+
try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
|
|
691
925
|
}
|
|
692
926
|
|
|
693
927
|
test "Chance.multiHit" {
|
|
@@ -695,25 +929,142 @@ test "Chance.multiHit" {
|
|
|
695
929
|
|
|
696
930
|
try chance.multiHit(.P1, 3);
|
|
697
931
|
try expectProbability(&chance.probability, 3, 8);
|
|
698
|
-
try expectValue(3, chance.actions.p1.multi_hit);
|
|
932
|
+
try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
|
|
699
933
|
|
|
700
934
|
chance.reset();
|
|
701
935
|
|
|
702
936
|
try chance.multiHit(.P2, 5);
|
|
703
937
|
try expectProbability(&chance.probability, 1, 8);
|
|
704
|
-
try expectValue(5, chance.actions.p2.multi_hit);
|
|
938
|
+
try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
|
|
705
939
|
}
|
|
706
940
|
|
|
707
941
|
test "Chance.duration" {
|
|
708
942
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
709
943
|
|
|
710
944
|
chance.duration(.P1, 2);
|
|
711
|
-
try expectValue(2, chance.actions.p1.duration);
|
|
945
|
+
try expectValue(@as(u4, 2), chance.actions.p1.duration);
|
|
712
946
|
|
|
713
947
|
chance.reset();
|
|
714
948
|
|
|
715
949
|
chance.duration(.P2, 4);
|
|
716
|
-
try expectValue(4, chance.actions.p2.duration);
|
|
950
|
+
try expectValue(@as(u4, 4), chance.actions.p2.duration);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
test "Chance.sleep" {
|
|
954
|
+
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
955
|
+
|
|
956
|
+
for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
|
|
957
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
958
|
+
try chance.sleep(.P1, .ended);
|
|
959
|
+
try expectProbability(&chance.probability, 1, d);
|
|
960
|
+
try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
961
|
+
|
|
962
|
+
chance.reset();
|
|
963
|
+
|
|
964
|
+
if (i < 7) {
|
|
965
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
966
|
+
try chance.sleep(.P1, .continuing);
|
|
967
|
+
try expectProbability(&chance.probability, d - 1, d);
|
|
968
|
+
try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
969
|
+
|
|
970
|
+
chance.reset();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
test "Chance.confusion" {
|
|
976
|
+
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
977
|
+
|
|
978
|
+
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
979
|
+
if (i > 1) {
|
|
980
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
981
|
+
try chance.confusion(.P2, .ended);
|
|
982
|
+
try expectProbability(&chance.probability, 1, d);
|
|
983
|
+
try expectValue(@as(u3, 0), chance.durations.p2.confusion);
|
|
984
|
+
|
|
985
|
+
chance.reset();
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (i < 5) {
|
|
989
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
990
|
+
try chance.confusion(.P2, .continuing);
|
|
991
|
+
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
992
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
|
|
993
|
+
|
|
994
|
+
chance.reset();
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
test "Chance.disable" {
|
|
1000
|
+
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1001
|
+
|
|
1002
|
+
for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
1003
|
+
chance.durations.p1.disable = @intCast(i);
|
|
1004
|
+
try chance.disable(.P1, .ended);
|
|
1005
|
+
try expectProbability(&chance.probability, 1, d);
|
|
1006
|
+
try expectValue(@as(u4, 0), chance.durations.p1.disable);
|
|
1007
|
+
|
|
1008
|
+
chance.reset();
|
|
1009
|
+
|
|
1010
|
+
if (i < 8) {
|
|
1011
|
+
chance.durations.p1.disable = @intCast(i);
|
|
1012
|
+
try chance.disable(.P1, .continuing);
|
|
1013
|
+
try expectProbability(&chance.probability, d - 1, d);
|
|
1014
|
+
try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
|
|
1015
|
+
|
|
1016
|
+
chance.reset();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
test "Chance.attacking" {
|
|
1022
|
+
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1023
|
+
|
|
1024
|
+
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
1025
|
+
if (i > 1) {
|
|
1026
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1027
|
+
try chance.attacking(.P2, .ended);
|
|
1028
|
+
try expectProbability(&chance.probability, 1, d);
|
|
1029
|
+
try expectValue(@as(u3, 0), chance.durations.p2.attacking);
|
|
1030
|
+
|
|
1031
|
+
chance.reset();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (i < 3) {
|
|
1035
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1036
|
+
try chance.attacking(.P2, .continuing);
|
|
1037
|
+
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
1038
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
|
|
1039
|
+
|
|
1040
|
+
chance.reset();
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
test "Chance.binding" {
|
|
1046
|
+
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
1047
|
+
|
|
1048
|
+
const ps = [_]u8{ 3, 3, 1, 1 };
|
|
1049
|
+
const qs = [_]u8{ 8, 5, 2, 1 };
|
|
1050
|
+
|
|
1051
|
+
for (ps, qs, 1..5) |p, q, i| {
|
|
1052
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1053
|
+
try chance.binding(.P1, .ended);
|
|
1054
|
+
try expectProbability(&chance.probability, p, q);
|
|
1055
|
+
try expectValue(@as(u3, 0), chance.durations.p1.binding);
|
|
1056
|
+
|
|
1057
|
+
chance.reset();
|
|
1058
|
+
|
|
1059
|
+
if (i < 4) {
|
|
1060
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1061
|
+
try chance.binding(.P1, .continuing);
|
|
1062
|
+
try expectProbability(&chance.probability, q - p, q);
|
|
1063
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
|
|
1064
|
+
|
|
1065
|
+
chance.reset();
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
717
1068
|
}
|
|
718
1069
|
|
|
719
1070
|
test "Chance.psywave" {
|
|
@@ -721,7 +1072,7 @@ test "Chance.psywave" {
|
|
|
721
1072
|
|
|
722
1073
|
try chance.psywave(.P2, 100, 150);
|
|
723
1074
|
try expectProbability(&chance.probability, 1, 150);
|
|
724
|
-
try expectValue(101, chance.actions.p2.psywave);
|
|
1075
|
+
try expectValue(@as(u8, 101), chance.actions.p2.psywave);
|
|
725
1076
|
}
|
|
726
1077
|
|
|
727
1078
|
test "Chance.metronome" {
|
|
@@ -737,7 +1088,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
|
737
1088
|
|
|
738
1089
|
r.reduce();
|
|
739
1090
|
if (r.p != p or r.q != q) {
|
|
740
|
-
print("expected {d}/{d}, found {}\n", .{ p, q, r });
|
|
1091
|
+
print("expected {d}/{d}, found {f}\n", .{ p, q, r });
|
|
741
1092
|
return error.TestExpectedEqual;
|
|
742
1093
|
}
|
|
743
1094
|
}
|
|
@@ -759,12 +1110,16 @@ const Null = struct {
|
|
|
759
1110
|
_ = .{ self, player, kind };
|
|
760
1111
|
}
|
|
761
1112
|
|
|
1113
|
+
pub fn glitch(self: Null) void {
|
|
1114
|
+
_ = .{self};
|
|
1115
|
+
}
|
|
1116
|
+
|
|
762
1117
|
pub fn clearPending(self: Null) void {
|
|
763
1118
|
_ = .{self};
|
|
764
1119
|
}
|
|
765
1120
|
|
|
766
|
-
pub fn switched(self: Null, player: Player,
|
|
767
|
-
_ = .{ self, player,
|
|
1121
|
+
pub fn switched(self: Null, player: Player, slot: u8) void {
|
|
1122
|
+
_ = .{ self, player, slot };
|
|
768
1123
|
}
|
|
769
1124
|
|
|
770
1125
|
pub fn speedTie(self: Null, p1: bool) Error!void {
|
|
@@ -803,17 +1158,53 @@ const Null = struct {
|
|
|
803
1158
|
_ = .{ self, player, n };
|
|
804
1159
|
}
|
|
805
1160
|
|
|
1161
|
+
pub fn duration(self: Null, player: Player, turns: u4) void {
|
|
1162
|
+
_ = .{ self, player, turns };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
806
1165
|
pub fn observe(
|
|
807
1166
|
self: Null,
|
|
808
1167
|
comptime field: Action.Field,
|
|
809
1168
|
player: Player,
|
|
810
|
-
|
|
1169
|
+
obs: Optional(Observation(.confusion)),
|
|
811
1170
|
) void {
|
|
812
|
-
_ = .{ self, field, player,
|
|
1171
|
+
_ = .{ self, field, player, obs };
|
|
813
1172
|
}
|
|
814
1173
|
|
|
815
|
-
pub fn
|
|
816
|
-
_ = .{ self, player,
|
|
1174
|
+
pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
|
|
1175
|
+
_ = .{ self, player, obs };
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
pub fn confusion(
|
|
1179
|
+
self: Null,
|
|
1180
|
+
player: Player,
|
|
1181
|
+
obs: Optional(Observation(.confusion)),
|
|
1182
|
+
) Error!void {
|
|
1183
|
+
_ = .{ self, player, obs };
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
pub fn disable(
|
|
1187
|
+
self: Null,
|
|
1188
|
+
player: Player,
|
|
1189
|
+
obs: Optional(Observation(.disable)),
|
|
1190
|
+
) Error!void {
|
|
1191
|
+
_ = .{ self, player, obs };
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
pub fn attacking(
|
|
1195
|
+
self: Null,
|
|
1196
|
+
player: Player,
|
|
1197
|
+
obs: Optional(Observation(.attacking)),
|
|
1198
|
+
) Error!void {
|
|
1199
|
+
_ = .{ self, player, obs };
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
pub fn binding(
|
|
1203
|
+
self: Null,
|
|
1204
|
+
player: Player,
|
|
1205
|
+
obs: Optional(Observation(.binding)),
|
|
1206
|
+
) Error!void {
|
|
1207
|
+
_ = .{ self, player, obs };
|
|
817
1208
|
}
|
|
818
1209
|
|
|
819
1210
|
pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
|
|
@@ -823,4 +1214,9 @@ const Null = struct {
|
|
|
823
1214
|
pub fn metronome(self: Null, player: Player, move: Move) Error!void {
|
|
824
1215
|
_ = .{ self, player, move };
|
|
825
1216
|
}
|
|
1217
|
+
|
|
1218
|
+
pub fn pp(self: Null, player: Player, n: u4) bool {
|
|
1219
|
+
_ = .{ self, player, n };
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
826
1222
|
};
|