@pkmn/engine 0.1.0-dev.62399ed5 → 0.1.0-dev.62eefbc0
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 +24 -14
- 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 +29 -1
- package/build/pkg/protocol.js +105 -103
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +83 -73
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +66 -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 +360 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +157 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +266 -103
- 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 +33 -28
- 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 +196 -160
- 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 +569 -190
- package/src/lib/gen1/chance.zig +412 -249
- 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 -556
- package/src/lib/gen1/mechanics.zig +317 -221
- 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 +9 -20
- package/src/lib/gen2/mechanics.zig +105 -132
- 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 +108 -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,8 +116,43 @@ test Actions {
|
|
|
112
116
|
try expect(b.matches(a));
|
|
113
117
|
try expect(!a.matches(c));
|
|
114
118
|
try expect(!c.matches(a));
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
}
|
|
120
|
+
|
|
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
|
+
};
|
|
117
156
|
}
|
|
118
157
|
|
|
119
158
|
/// Information about the RNG that was observed during a Generation I battle `update` for a
|
|
@@ -140,14 +179,25 @@ pub const Action = packed struct(u64) {
|
|
|
140
179
|
/// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
|
|
141
180
|
duration: u4 = 0,
|
|
142
181
|
|
|
143
|
-
///
|
|
144
|
-
|
|
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,
|
|
145
192
|
|
|
146
193
|
/// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
|
|
147
194
|
/// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
|
|
148
|
-
move_slot:
|
|
195
|
+
move_slot: u3 = 0,
|
|
149
196
|
/// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
|
|
150
|
-
multi_hit:
|
|
197
|
+
multi_hit: u3 = 0,
|
|
198
|
+
|
|
199
|
+
/// TODO
|
|
200
|
+
pp: u4 = 0,
|
|
151
201
|
|
|
152
202
|
/// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
|
|
153
203
|
psywave: u8 = 0,
|
|
@@ -155,39 +205,21 @@ pub const Action = packed struct(u64) {
|
|
|
155
205
|
/// If not None, the Move to return for Rolls.metronome.
|
|
156
206
|
metronome: Move = .None,
|
|
157
207
|
|
|
158
|
-
pub const DURATIONS: u64 = 0x000000FFFF000000;
|
|
159
|
-
|
|
160
208
|
pub const Field = std.meta.FieldEnum(Action);
|
|
161
209
|
|
|
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 };
|
|
210
|
+
pub fn format(a: Action, w: *std.Io.Writer) !void {
|
|
169
211
|
try fmt(a, w, false);
|
|
170
212
|
}
|
|
171
213
|
|
|
172
|
-
|
|
214
|
+
const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
|
|
215
|
+
|
|
216
|
+
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
173
217
|
try writer.writeByte('(');
|
|
174
218
|
var printed = false;
|
|
175
|
-
inline for (@typeInfo(Action).
|
|
219
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
176
220
|
const val = @field(self, field.name);
|
|
177
221
|
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) {
|
|
222
|
+
.@"enum" => if (val != .None) {
|
|
191
223
|
if (printed) try writer.writeAll(", ");
|
|
192
224
|
if (shape) {
|
|
193
225
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -196,12 +228,14 @@ pub const Action = packed struct(u64) {
|
|
|
196
228
|
if (val == .false) "!" else "",
|
|
197
229
|
field.name,
|
|
198
230
|
});
|
|
231
|
+
} else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
|
|
232
|
+
try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field.name });
|
|
199
233
|
} else {
|
|
200
234
|
try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
|
|
201
235
|
}
|
|
202
236
|
printed = true;
|
|
203
237
|
},
|
|
204
|
-
.
|
|
238
|
+
.int => if (val != 0) {
|
|
205
239
|
if (printed) try writer.writeAll(", ");
|
|
206
240
|
if (shape) {
|
|
207
241
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -217,43 +251,73 @@ pub const Action = packed struct(u64) {
|
|
|
217
251
|
}
|
|
218
252
|
};
|
|
219
253
|
|
|
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
254
|
/// TODO
|
|
228
255
|
pub const Durations = extern struct {
|
|
229
|
-
///
|
|
256
|
+
/// TODO
|
|
230
257
|
p1: Duration = .{},
|
|
231
|
-
///
|
|
258
|
+
/// TODO
|
|
232
259
|
p2: Duration = .{},
|
|
233
260
|
|
|
234
261
|
comptime {
|
|
235
|
-
assert(@sizeOf(Durations) ==
|
|
262
|
+
assert(@sizeOf(Durations) == 8);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// TODO
|
|
266
|
+
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
|
|
267
|
+
assert(isPointerTo(self, Durations));
|
|
268
|
+
return if (player == .P1) &self.p1 else &self.p2;
|
|
269
|
+
}
|
|
270
|
+
|
|
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
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
/// The number of turns a Pokémon has been observed to be confused.
|
|
276
|
+
pub const Sleeps = Array(6, u3);
|
|
277
|
+
|
|
278
|
+
/// TODO
|
|
279
|
+
pub const Duration = packed struct(u32) {
|
|
280
|
+
sleeps: Sleeps.T = 0,
|
|
245
281
|
confusion: u3 = 0,
|
|
246
|
-
/// The number of turns a Pokémon has been observed to be disabled.
|
|
247
282
|
disable: u4 = 0,
|
|
248
|
-
/// The number of turns a Pokémon has been observed to be attacking.
|
|
249
283
|
attacking: u3 = 0,
|
|
250
|
-
/// The number of turns a Pokémon has been observed to be binding their opponent.
|
|
251
284
|
binding: u3 = 0,
|
|
252
285
|
|
|
253
|
-
|
|
286
|
+
_: u1 = 0,
|
|
287
|
+
|
|
288
|
+
pub const Field = enum { attacking, binding, sleep, disable, confusion };
|
|
289
|
+
|
|
290
|
+
pub fn format(
|
|
291
|
+
self: Duration,
|
|
292
|
+
writer: *std.Io.Writer,
|
|
293
|
+
) !void {
|
|
294
|
+
try writer.writeByte('(');
|
|
295
|
+
var printed = false;
|
|
296
|
+
inline for (@typeInfo(Duration).@"struct".fields) |field| {
|
|
297
|
+
const val = @field(self, field.name);
|
|
298
|
+
switch (@typeInfo(@TypeOf(val))) {
|
|
299
|
+
.int => if (val != 0) {
|
|
300
|
+
if (printed) try writer.writeAll(", ");
|
|
301
|
+
if (comptime std.mem.eql(u8, field.name, "sleeps")) {
|
|
302
|
+
try writer.print("{s}:[", .{field.name});
|
|
303
|
+
for (0..6) |i| {
|
|
304
|
+
if (i != 0) try writer.writeByte(',');
|
|
305
|
+
try writer.print("{d}", .{Sleeps.get(val, i)});
|
|
306
|
+
}
|
|
307
|
+
try writer.writeAll("]");
|
|
308
|
+
} else {
|
|
309
|
+
try writer.print("{s}:{d}", .{ field.name, val });
|
|
310
|
+
}
|
|
311
|
+
printed = true;
|
|
312
|
+
},
|
|
313
|
+
else => unreachable,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
try writer.writeByte(')');
|
|
317
|
+
}
|
|
254
318
|
};
|
|
255
319
|
|
|
256
|
-
pub const Commit = enum { hit, miss, binding };
|
|
320
|
+
pub const Commit = enum { hit, miss, binding, err, glitch };
|
|
257
321
|
|
|
258
322
|
const uN = if (options.miss) u8 else u9;
|
|
259
323
|
|
|
@@ -268,14 +332,8 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
268
332
|
/// The actions taken by a hypothetical "chance player" that convey information about which
|
|
269
333
|
/// RNG events were observed during a battle `update`.
|
|
270
334
|
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 },
|
|
335
|
+
/// TODO
|
|
336
|
+
durations: Durations = .{},
|
|
279
337
|
|
|
280
338
|
// In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
|
|
281
339
|
// later checks render their result irrelevant (missing when the target is actually immune,
|
|
@@ -299,7 +357,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
299
357
|
damage_roll: u8 = 0,
|
|
300
358
|
hit: bool = false,
|
|
301
359
|
hit_probablity: uN = 0,
|
|
302
|
-
|
|
360
|
+
glitch: bool = false,
|
|
303
361
|
} = .{},
|
|
304
362
|
|
|
305
363
|
/// Possible error returned by operations tracking chance probability.
|
|
@@ -310,7 +368,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
310
368
|
if (!enabled) return;
|
|
311
369
|
|
|
312
370
|
self.probability.reset();
|
|
313
|
-
self.actions.
|
|
371
|
+
self.actions = .{};
|
|
314
372
|
self.pending = .{};
|
|
315
373
|
}
|
|
316
374
|
|
|
@@ -319,20 +377,18 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
319
377
|
|
|
320
378
|
assert(!showdown);
|
|
321
379
|
|
|
380
|
+
if (kind == .glitch and !self.pending.glitch) return;
|
|
381
|
+
|
|
322
382
|
var action = self.actions.get(player);
|
|
323
383
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
action.duration = @intCast(self.pending.binding);
|
|
329
|
-
assert(action.durations.binding == 0);
|
|
330
|
-
action.durations.binding = 1;
|
|
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;
|
|
331
388
|
}
|
|
332
|
-
if (kind == .binding) return;
|
|
333
389
|
|
|
334
390
|
// Always commit the hit result if we make it here (commit won't be called at all if the
|
|
335
|
-
// target is immune
|
|
391
|
+
// target is immune*/behind a sub/etc)
|
|
336
392
|
if (self.pending.hit_probablity != 0) {
|
|
337
393
|
try self.probability.update(self.pending.hit_probablity, 256);
|
|
338
394
|
action.hit = if (self.pending.hit) .true else .false;
|
|
@@ -340,57 +396,59 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
340
396
|
|
|
341
397
|
// If the move actually lands we can commit any past critical hit / damage rolls. Note
|
|
342
398
|
// 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
|
|
399
|
+
// "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
|
|
344
400
|
// 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
|
-
|
|
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
|
+
}
|
|
347
409
|
|
|
348
410
|
if (self.pending.crit_probablity != 0) {
|
|
349
411
|
try self.probability.update(self.pending.crit_probablity, 256);
|
|
350
412
|
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
351
|
-
}
|
|
413
|
+
} else assert(!self.pending.glitch);
|
|
352
414
|
|
|
353
|
-
if (self.pending.damage_roll > 0) {
|
|
415
|
+
if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
|
|
354
416
|
try self.probability.update(1, 39);
|
|
355
417
|
action.damage = self.pending.damage_roll;
|
|
356
418
|
}
|
|
357
419
|
}
|
|
358
420
|
|
|
359
|
-
pub fn
|
|
421
|
+
pub fn glitch(self: *Self) void {
|
|
360
422
|
if (!enabled) return;
|
|
361
423
|
|
|
362
|
-
|
|
424
|
+
assert(!showdown);
|
|
425
|
+
self.pending.glitch = true;
|
|
363
426
|
}
|
|
364
427
|
|
|
365
|
-
pub fn
|
|
428
|
+
pub fn clearPending(self: *Self) void {
|
|
366
429
|
if (!enabled) return;
|
|
367
430
|
|
|
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
|
-
}
|
|
431
|
+
assert(!showdown);
|
|
432
|
+
self.pending = .{};
|
|
377
433
|
}
|
|
378
434
|
|
|
379
|
-
pub fn switched(self: *Self, player: Player,
|
|
435
|
+
pub fn switched(self: *Self, player: Player, slot: u8) void {
|
|
380
436
|
if (!enabled) return;
|
|
381
437
|
|
|
382
|
-
assert(
|
|
383
|
-
assert(out >= 1 and out <= 6);
|
|
438
|
+
assert(slot >= 1 and slot <= 6);
|
|
384
439
|
|
|
385
|
-
var
|
|
440
|
+
var d = self.durations.get(player);
|
|
386
441
|
|
|
387
|
-
const
|
|
388
|
-
|
|
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);
|
|
389
445
|
|
|
390
|
-
|
|
391
|
-
|
|
446
|
+
d.confusion = 0;
|
|
447
|
+
d.disable = 0;
|
|
448
|
+
d.attacking = 0;
|
|
449
|
+
d.binding = 0;
|
|
392
450
|
|
|
393
|
-
self.
|
|
451
|
+
self.durations.get(player.foe()).binding = 0;
|
|
394
452
|
}
|
|
395
453
|
|
|
396
454
|
pub fn speedTie(self: *Self, p1: bool) Error!void {
|
|
@@ -489,108 +547,188 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
489
547
|
self.actions.get(player).multi_hit = n;
|
|
490
548
|
}
|
|
491
549
|
|
|
492
|
-
pub fn duration(
|
|
550
|
+
pub fn duration(self: *Self, player: Player, turns: u4) void {
|
|
551
|
+
if (!enabled) return;
|
|
552
|
+
|
|
553
|
+
self.actions.get(player).duration = if (options.key) 2 else turns;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
pub fn observe(
|
|
493
557
|
self: *Self,
|
|
494
|
-
comptime field:
|
|
558
|
+
comptime field: Action.Field,
|
|
495
559
|
player: Player,
|
|
496
|
-
|
|
497
|
-
turns: u4,
|
|
560
|
+
obs: Optional(Observation(.confusion)),
|
|
498
561
|
) void {
|
|
499
562
|
if (!enabled) return;
|
|
500
563
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
(
|
|
507
|
-
|
|
508
|
-
|
|
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,
|
|
509
599
|
}
|
|
510
600
|
}
|
|
511
601
|
|
|
512
|
-
pub fn sleep(self: *Self, player: Player,
|
|
602
|
+
pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
|
|
513
603
|
if (!enabled) return;
|
|
514
604
|
|
|
515
|
-
var
|
|
516
|
-
|
|
517
|
-
|
|
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) {
|
|
518
615
|
assert(n >= 1 and n <= 7);
|
|
519
616
|
if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
|
|
520
|
-
|
|
617
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
521
618
|
} else {
|
|
619
|
+
assert(obs == .continuing);
|
|
522
620
|
assert(n >= 1 and n < 7);
|
|
523
621
|
try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
|
|
524
|
-
|
|
622
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
|
|
525
623
|
}
|
|
526
624
|
}
|
|
527
625
|
|
|
528
|
-
pub fn confusion(
|
|
626
|
+
pub fn confusion(
|
|
627
|
+
self: *Self,
|
|
628
|
+
player: Player,
|
|
629
|
+
obs: Optional(Observation(.confusion)),
|
|
630
|
+
) Error!void {
|
|
529
631
|
if (!enabled) return;
|
|
530
632
|
|
|
531
|
-
var
|
|
532
|
-
|
|
533
|
-
|
|
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) {
|
|
534
641
|
assert(n >= 2 and n <= 5);
|
|
535
642
|
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
536
|
-
|
|
643
|
+
d.confusion = 0;
|
|
537
644
|
} else {
|
|
645
|
+
assert(obs == .continuing);
|
|
538
646
|
assert(n >= 1 and n < 5);
|
|
539
647
|
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
540
|
-
|
|
648
|
+
d.confusion += 1;
|
|
541
649
|
}
|
|
542
650
|
}
|
|
543
651
|
|
|
544
|
-
pub fn disable(
|
|
652
|
+
pub fn disable(
|
|
653
|
+
self: *Self,
|
|
654
|
+
player: Player,
|
|
655
|
+
obs: Optional(Observation(.disable)),
|
|
656
|
+
) Error!void {
|
|
545
657
|
if (!enabled) return;
|
|
546
658
|
|
|
547
|
-
var
|
|
548
|
-
|
|
549
|
-
|
|
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) {
|
|
550
667
|
assert(n >= 1 and n <= 8);
|
|
551
668
|
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
552
|
-
|
|
669
|
+
d.disable = 0;
|
|
553
670
|
} else {
|
|
671
|
+
assert(obs == .continuing);
|
|
554
672
|
assert(n >= 1 and n < 8);
|
|
555
673
|
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
556
|
-
|
|
674
|
+
d.disable += 1;
|
|
557
675
|
}
|
|
558
676
|
}
|
|
559
677
|
|
|
560
|
-
pub fn attacking(
|
|
678
|
+
pub fn attacking(
|
|
679
|
+
self: *Self,
|
|
680
|
+
player: Player,
|
|
681
|
+
obs: Optional(Observation(.attacking)),
|
|
682
|
+
) Error!void {
|
|
561
683
|
if (!enabled) return;
|
|
562
684
|
|
|
563
|
-
var
|
|
564
|
-
|
|
565
|
-
|
|
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
|
|
566
693
|
assert(n >= 2 and n <= 3);
|
|
567
694
|
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
568
|
-
|
|
695
|
+
d.attacking = 0;
|
|
569
696
|
} else {
|
|
697
|
+
assert(obs == .continuing);
|
|
570
698
|
assert(n >= 1 and n < 3);
|
|
571
699
|
if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
|
|
572
|
-
|
|
700
|
+
d.attacking += 1;
|
|
573
701
|
}
|
|
574
702
|
}
|
|
575
703
|
|
|
576
|
-
pub fn binding(
|
|
704
|
+
pub fn binding(
|
|
705
|
+
self: *Self,
|
|
706
|
+
player: Player,
|
|
707
|
+
obs: Optional(Observation(.binding)),
|
|
708
|
+
) Error!void {
|
|
577
709
|
if (!enabled) return;
|
|
578
710
|
|
|
579
|
-
var
|
|
580
|
-
|
|
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;
|
|
581
717
|
|
|
582
718
|
assert(n > 0);
|
|
583
719
|
const p: u4 = if (n < 3) 3 else 1;
|
|
584
720
|
const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
|
|
585
721
|
|
|
586
|
-
if (
|
|
722
|
+
if (obs == .ended) {
|
|
723
|
+
// "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
587
724
|
assert(n >= 1 and n <= 4);
|
|
588
725
|
if (n != 4) try self.probability.update(p, q);
|
|
589
|
-
|
|
726
|
+
d.binding = 0;
|
|
590
727
|
} else {
|
|
728
|
+
assert(obs == .continuing);
|
|
591
729
|
assert(n >= 1 and n < 4);
|
|
592
730
|
try self.probability.update(q - p, q);
|
|
593
|
-
|
|
731
|
+
d.binding += 1;
|
|
594
732
|
}
|
|
595
733
|
}
|
|
596
734
|
|
|
@@ -607,6 +745,15 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
607
745
|
try self.probability.update(1, Move.METRONOME.len);
|
|
608
746
|
self.actions.get(player).metronome = move;
|
|
609
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
|
+
}
|
|
610
757
|
};
|
|
611
758
|
}
|
|
612
759
|
|
|
@@ -707,16 +854,16 @@ test "Chance.damage" {
|
|
|
707
854
|
|
|
708
855
|
try chance.damage(.P1, 217);
|
|
709
856
|
if (showdown) {
|
|
710
|
-
try expectValue(217, chance.actions.p1.damage);
|
|
857
|
+
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
711
858
|
try expectProbability(&chance.probability, 1, 39);
|
|
712
859
|
} else {
|
|
713
860
|
try expectProbability(&chance.probability, 1, 1);
|
|
714
|
-
try expectValue(0, chance.actions.p1.damage);
|
|
861
|
+
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
715
862
|
|
|
716
863
|
try chance.commit(.P1, .hit);
|
|
717
864
|
|
|
718
865
|
try expectProbability(&chance.probability, 1, 39);
|
|
719
|
-
try expectValue(217, chance.actions.p1.damage);
|
|
866
|
+
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
720
867
|
}
|
|
721
868
|
}
|
|
722
869
|
|
|
@@ -768,13 +915,13 @@ test "Chance.moveSlot" {
|
|
|
768
915
|
|
|
769
916
|
try chance.moveSlot(.P2, 2, &ms, 2);
|
|
770
917
|
try expectProbability(&chance.probability, 1, 2);
|
|
771
|
-
try expectValue(2, chance.actions.p2.move_slot);
|
|
918
|
+
try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
|
|
772
919
|
|
|
773
920
|
chance.reset();
|
|
774
921
|
|
|
775
922
|
try chance.moveSlot(.P1, 1, &ms, 0);
|
|
776
923
|
try expectProbability(&chance.probability, 1, 3);
|
|
777
|
-
try expectValue(1, chance.actions.p1.move_slot);
|
|
924
|
+
try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
|
|
778
925
|
}
|
|
779
926
|
|
|
780
927
|
test "Chance.multiHit" {
|
|
@@ -782,51 +929,43 @@ test "Chance.multiHit" {
|
|
|
782
929
|
|
|
783
930
|
try chance.multiHit(.P1, 3);
|
|
784
931
|
try expectProbability(&chance.probability, 3, 8);
|
|
785
|
-
try expectValue(3, chance.actions.p1.multi_hit);
|
|
932
|
+
try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
|
|
786
933
|
|
|
787
934
|
chance.reset();
|
|
788
935
|
|
|
789
936
|
try chance.multiHit(.P2, 5);
|
|
790
937
|
try expectProbability(&chance.probability, 1, 8);
|
|
791
|
-
try expectValue(5, chance.actions.p2.multi_hit);
|
|
938
|
+
try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
|
|
792
939
|
}
|
|
793
940
|
|
|
794
941
|
test "Chance.duration" {
|
|
795
942
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
796
943
|
|
|
797
|
-
chance.duration(.
|
|
798
|
-
try expectValue(2, chance.actions.p1.duration);
|
|
799
|
-
try expectValue(1, chance.actions.p2.durations.sleep);
|
|
944
|
+
chance.duration(.P1, 2);
|
|
945
|
+
try expectValue(@as(u4, 2), chance.actions.p1.duration);
|
|
800
946
|
|
|
801
947
|
chance.reset();
|
|
802
948
|
|
|
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);
|
|
949
|
+
chance.duration(.P2, 4);
|
|
950
|
+
try expectValue(@as(u4, 4), chance.actions.p2.duration);
|
|
812
951
|
}
|
|
813
952
|
|
|
814
953
|
test "Chance.sleep" {
|
|
815
954
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
816
955
|
|
|
817
956
|
for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
|
|
818
|
-
chance.
|
|
819
|
-
try chance.sleep(.P1,
|
|
957
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
958
|
+
try chance.sleep(.P1, .ended);
|
|
820
959
|
try expectProbability(&chance.probability, 1, d);
|
|
821
|
-
try expectValue(0, chance.
|
|
960
|
+
try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
822
961
|
|
|
823
962
|
chance.reset();
|
|
824
963
|
|
|
825
964
|
if (i < 7) {
|
|
826
|
-
chance.
|
|
827
|
-
try chance.sleep(.P1,
|
|
965
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
966
|
+
try chance.sleep(.P1, .continuing);
|
|
828
967
|
try expectProbability(&chance.probability, d - 1, d);
|
|
829
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
968
|
+
try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
830
969
|
|
|
831
970
|
chance.reset();
|
|
832
971
|
}
|
|
@@ -838,19 +977,19 @@ test "Chance.confusion" {
|
|
|
838
977
|
|
|
839
978
|
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
840
979
|
if (i > 1) {
|
|
841
|
-
chance.
|
|
842
|
-
try chance.confusion(.P2,
|
|
980
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
981
|
+
try chance.confusion(.P2, .ended);
|
|
843
982
|
try expectProbability(&chance.probability, 1, d);
|
|
844
|
-
try expectValue(0, chance.
|
|
983
|
+
try expectValue(@as(u3, 0), chance.durations.p2.confusion);
|
|
845
984
|
|
|
846
985
|
chance.reset();
|
|
847
986
|
}
|
|
848
987
|
|
|
849
988
|
if (i < 5) {
|
|
850
|
-
chance.
|
|
851
|
-
try chance.confusion(.P2,
|
|
989
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
990
|
+
try chance.confusion(.P2, .continuing);
|
|
852
991
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
853
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
992
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
|
|
854
993
|
|
|
855
994
|
chance.reset();
|
|
856
995
|
}
|
|
@@ -861,18 +1000,18 @@ test "Chance.disable" {
|
|
|
861
1000
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
862
1001
|
|
|
863
1002
|
for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
864
|
-
chance.
|
|
865
|
-
try chance.disable(.P1,
|
|
1003
|
+
chance.durations.p1.disable = @intCast(i);
|
|
1004
|
+
try chance.disable(.P1, .ended);
|
|
866
1005
|
try expectProbability(&chance.probability, 1, d);
|
|
867
|
-
try expectValue(0, chance.
|
|
1006
|
+
try expectValue(@as(u4, 0), chance.durations.p1.disable);
|
|
868
1007
|
|
|
869
1008
|
chance.reset();
|
|
870
1009
|
|
|
871
1010
|
if (i < 8) {
|
|
872
|
-
chance.
|
|
873
|
-
try chance.disable(.P1,
|
|
1011
|
+
chance.durations.p1.disable = @intCast(i);
|
|
1012
|
+
try chance.disable(.P1, .continuing);
|
|
874
1013
|
try expectProbability(&chance.probability, d - 1, d);
|
|
875
|
-
try expectValue(@as(u4, @intCast(i)) + 1, chance.
|
|
1014
|
+
try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
|
|
876
1015
|
|
|
877
1016
|
chance.reset();
|
|
878
1017
|
}
|
|
@@ -884,19 +1023,19 @@ test "Chance.attacking" {
|
|
|
884
1023
|
|
|
885
1024
|
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
886
1025
|
if (i > 1) {
|
|
887
|
-
chance.
|
|
888
|
-
try chance.attacking(.P2,
|
|
1026
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1027
|
+
try chance.attacking(.P2, .ended);
|
|
889
1028
|
try expectProbability(&chance.probability, 1, d);
|
|
890
|
-
try expectValue(0, chance.
|
|
1029
|
+
try expectValue(@as(u3, 0), chance.durations.p2.attacking);
|
|
891
1030
|
|
|
892
1031
|
chance.reset();
|
|
893
1032
|
}
|
|
894
1033
|
|
|
895
1034
|
if (i < 3) {
|
|
896
|
-
chance.
|
|
897
|
-
try chance.attacking(.P2,
|
|
1035
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1036
|
+
try chance.attacking(.P2, .continuing);
|
|
898
1037
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
899
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1038
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
|
|
900
1039
|
|
|
901
1040
|
chance.reset();
|
|
902
1041
|
}
|
|
@@ -910,18 +1049,18 @@ test "Chance.binding" {
|
|
|
910
1049
|
const qs = [_]u8{ 8, 5, 2, 1 };
|
|
911
1050
|
|
|
912
1051
|
for (ps, qs, 1..5) |p, q, i| {
|
|
913
|
-
chance.
|
|
914
|
-
try chance.binding(.P1,
|
|
1052
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1053
|
+
try chance.binding(.P1, .ended);
|
|
915
1054
|
try expectProbability(&chance.probability, p, q);
|
|
916
|
-
try expectValue(0, chance.
|
|
1055
|
+
try expectValue(@as(u3, 0), chance.durations.p1.binding);
|
|
917
1056
|
|
|
918
1057
|
chance.reset();
|
|
919
1058
|
|
|
920
1059
|
if (i < 4) {
|
|
921
|
-
chance.
|
|
922
|
-
try chance.binding(.P1,
|
|
1060
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1061
|
+
try chance.binding(.P1, .continuing);
|
|
923
1062
|
try expectProbability(&chance.probability, q - p, q);
|
|
924
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1063
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
|
|
925
1064
|
|
|
926
1065
|
chance.reset();
|
|
927
1066
|
}
|
|
@@ -933,7 +1072,7 @@ test "Chance.psywave" {
|
|
|
933
1072
|
|
|
934
1073
|
try chance.psywave(.P2, 100, 150);
|
|
935
1074
|
try expectProbability(&chance.probability, 1, 150);
|
|
936
|
-
try expectValue(101, chance.actions.p2.psywave);
|
|
1075
|
+
try expectValue(@as(u8, 101), chance.actions.p2.psywave);
|
|
937
1076
|
}
|
|
938
1077
|
|
|
939
1078
|
test "Chance.metronome" {
|
|
@@ -949,7 +1088,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
|
949
1088
|
|
|
950
1089
|
r.reduce();
|
|
951
1090
|
if (r.p != p or r.q != q) {
|
|
952
|
-
print("expected {d}/{d}, found {}\n", .{ p, q, r });
|
|
1091
|
+
print("expected {d}/{d}, found {f}\n", .{ p, q, r });
|
|
953
1092
|
return error.TestExpectedEqual;
|
|
954
1093
|
}
|
|
955
1094
|
}
|
|
@@ -971,16 +1110,16 @@ const Null = struct {
|
|
|
971
1110
|
_ = .{ self, player, kind };
|
|
972
1111
|
}
|
|
973
1112
|
|
|
974
|
-
pub fn
|
|
1113
|
+
pub fn glitch(self: Null) void {
|
|
975
1114
|
_ = .{self};
|
|
976
1115
|
}
|
|
977
1116
|
|
|
978
|
-
pub fn
|
|
979
|
-
_ = .{
|
|
1117
|
+
pub fn clearPending(self: Null) void {
|
|
1118
|
+
_ = .{self};
|
|
980
1119
|
}
|
|
981
1120
|
|
|
982
|
-
pub fn switched(self: Null, player: Player,
|
|
983
|
-
_ = .{ self, player,
|
|
1121
|
+
pub fn switched(self: Null, player: Player, slot: u8) void {
|
|
1122
|
+
_ = .{ self, player, slot };
|
|
984
1123
|
}
|
|
985
1124
|
|
|
986
1125
|
pub fn speedTie(self: Null, p1: bool) Error!void {
|
|
@@ -1019,34 +1158,53 @@ const Null = struct {
|
|
|
1019
1158
|
_ = .{ self, player, n };
|
|
1020
1159
|
}
|
|
1021
1160
|
|
|
1022
|
-
pub fn duration(
|
|
1161
|
+
pub fn duration(self: Null, player: Player, turns: u4) void {
|
|
1162
|
+
_ = .{ self, player, turns };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
pub fn observe(
|
|
1023
1166
|
self: Null,
|
|
1024
|
-
comptime field:
|
|
1167
|
+
comptime field: Action.Field,
|
|
1025
1168
|
player: Player,
|
|
1026
|
-
|
|
1027
|
-
turns: u4,
|
|
1169
|
+
obs: Optional(Observation(.confusion)),
|
|
1028
1170
|
) void {
|
|
1029
|
-
_ = .{ self, field, player,
|
|
1171
|
+
_ = .{ self, field, player, obs };
|
|
1030
1172
|
}
|
|
1031
1173
|
|
|
1032
|
-
pub fn sleep(self: Null, player: Player,
|
|
1033
|
-
_ = .{ self, player,
|
|
1174
|
+
pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
|
|
1175
|
+
_ = .{ self, player, obs };
|
|
1034
1176
|
}
|
|
1035
1177
|
|
|
1036
|
-
pub fn
|
|
1037
|
-
|
|
1178
|
+
pub fn confusion(
|
|
1179
|
+
self: Null,
|
|
1180
|
+
player: Player,
|
|
1181
|
+
obs: Optional(Observation(.confusion)),
|
|
1182
|
+
) Error!void {
|
|
1183
|
+
_ = .{ self, player, obs };
|
|
1038
1184
|
}
|
|
1039
1185
|
|
|
1040
|
-
pub fn
|
|
1041
|
-
|
|
1186
|
+
pub fn disable(
|
|
1187
|
+
self: Null,
|
|
1188
|
+
player: Player,
|
|
1189
|
+
obs: Optional(Observation(.disable)),
|
|
1190
|
+
) Error!void {
|
|
1191
|
+
_ = .{ self, player, obs };
|
|
1042
1192
|
}
|
|
1043
1193
|
|
|
1044
|
-
pub fn attacking(
|
|
1045
|
-
|
|
1194
|
+
pub fn attacking(
|
|
1195
|
+
self: Null,
|
|
1196
|
+
player: Player,
|
|
1197
|
+
obs: Optional(Observation(.attacking)),
|
|
1198
|
+
) Error!void {
|
|
1199
|
+
_ = .{ self, player, obs };
|
|
1046
1200
|
}
|
|
1047
1201
|
|
|
1048
|
-
pub fn binding(
|
|
1049
|
-
|
|
1202
|
+
pub fn binding(
|
|
1203
|
+
self: Null,
|
|
1204
|
+
player: Player,
|
|
1205
|
+
obs: Optional(Observation(.binding)),
|
|
1206
|
+
) Error!void {
|
|
1207
|
+
_ = .{ self, player, obs };
|
|
1050
1208
|
}
|
|
1051
1209
|
|
|
1052
1210
|
pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
|
|
@@ -1056,4 +1214,9 @@ const Null = struct {
|
|
|
1056
1214
|
pub fn metronome(self: Null, player: Player, move: Move) Error!void {
|
|
1057
1215
|
_ = .{ self, player, move };
|
|
1058
1216
|
}
|
|
1217
|
+
|
|
1218
|
+
pub fn pp(self: Null, player: Player, n: u4) bool {
|
|
1219
|
+
_ = .{ self, player, n };
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1059
1222
|
};
|