@pkmn/engine 0.1.0-dev.abd68573 → 0.1.0-dev.ac652f4e
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 +17 -12
- 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 +307 -78
- 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 +6 -0
- package/src/lib/common/protocol.zig +45 -44
- package/src/lib/common/rational.zig +323 -158
- package/src/lib/common/rng.zig +5 -7
- package/src/lib/common/util.zig +60 -1
- package/src/lib/gen1/calc.zig +693 -157
- package/src/lib/gen1/chance.zig +415 -291
- 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 +38 -41
- package/src/lib/gen1/helpers.zig +64 -540
- package/src/lib/gen1/mechanics.zig +332 -244
- 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 +46 -25
- 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,82 @@ 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 };
|
|
316
|
+
|
|
317
|
+
const uN = if (options.miss) u8 else u9;
|
|
257
318
|
|
|
258
319
|
/// Tracks chance actions and their associated probability during a Generation I battle update when
|
|
259
320
|
/// `options.chance` is enabled.
|
|
@@ -266,14 +327,8 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
266
327
|
/// The actions taken by a hypothetical "chance player" that convey information about which
|
|
267
328
|
/// RNG events were observed during a battle `update`.
|
|
268
329
|
actions: Actions = .{},
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// other durations which are all tied to volatiles, sleep's counter persists through
|
|
272
|
-
// switching and so we must store it here. The indices of these arrays correspond to the
|
|
273
|
-
// `order` field of a Side. This information could be stored in actions but size is a
|
|
274
|
-
// concern so this is tracked separately as actions only purports to track RNG events
|
|
275
|
-
// observed during a single `update` (not across updates).
|
|
276
|
-
sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
|
|
330
|
+
/// TODO
|
|
331
|
+
durations: Durations = .{},
|
|
277
332
|
|
|
278
333
|
// In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
|
|
279
334
|
// later checks render their result irrelevant (missing when the target is actually immune,
|
|
@@ -291,16 +346,13 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
291
346
|
//
|
|
292
347
|
// Finally, because each player's actions are processed sequentially we only need a single
|
|
293
348
|
// shared structure to track this information (though it needs to be cleared appropriately)
|
|
294
|
-
pending: if (showdown) struct {
|
|
295
|
-
hit: bool = false,
|
|
296
|
-
hit_probablity: u8 = 0,
|
|
297
|
-
} else struct {
|
|
349
|
+
pending: if (showdown) struct {} else struct {
|
|
298
350
|
crit: bool = false,
|
|
299
351
|
crit_probablity: u8 = 0,
|
|
300
352
|
damage_roll: u8 = 0,
|
|
301
353
|
hit: bool = false,
|
|
302
|
-
hit_probablity:
|
|
303
|
-
|
|
354
|
+
hit_probablity: uN = 0,
|
|
355
|
+
glitch: bool = false,
|
|
304
356
|
} = .{},
|
|
305
357
|
|
|
306
358
|
/// Possible error returned by operations tracking chance probability.
|
|
@@ -308,86 +360,90 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
308
360
|
|
|
309
361
|
/// Convenience helper to clear fields which typically should be cleared between updates.
|
|
310
362
|
pub fn reset(self: *Self) void {
|
|
363
|
+
if (!enabled) return;
|
|
364
|
+
|
|
311
365
|
self.probability.reset();
|
|
312
|
-
self.actions.
|
|
366
|
+
self.actions = .{};
|
|
313
367
|
self.pending = .{};
|
|
314
368
|
}
|
|
315
369
|
|
|
316
370
|
pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
|
|
371
|
+
if (!enabled) return;
|
|
372
|
+
|
|
373
|
+
assert(!showdown);
|
|
374
|
+
|
|
375
|
+
if (kind == .glitch and !self.pending.glitch) return;
|
|
376
|
+
|
|
317
377
|
var action = self.actions.get(player);
|
|
318
378
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
action.duration = @intCast(self.pending.binding);
|
|
325
|
-
assert(action.durations.binding == 0);
|
|
326
|
-
action.durations.binding = 1;
|
|
327
|
-
}
|
|
328
|
-
if (kind == .binding) return;
|
|
329
|
-
} else assert(kind != .binding);
|
|
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;
|
|
383
|
+
}
|
|
330
384
|
|
|
331
385
|
// Always commit the hit result if we make it here (commit won't be called at all if the
|
|
332
|
-
// target is immune
|
|
386
|
+
// target is immune*/behind a sub/etc)
|
|
333
387
|
if (self.pending.hit_probablity != 0) {
|
|
334
388
|
try self.probability.update(self.pending.hit_probablity, 256);
|
|
335
389
|
action.hit = if (self.pending.hit) .true else .false;
|
|
336
390
|
}
|
|
337
391
|
|
|
338
|
-
|
|
392
|
+
// If the move actually lands we can commit any past critical hit / damage rolls. Note
|
|
393
|
+
// we can't rely on the presence or absence of a damage roll to determine whether a
|
|
394
|
+
// "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
|
|
395
|
+
// damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
|
|
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
|
+
}
|
|
339
404
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
assert(!self.pending.crit or self.pending.crit_probablity > 0);
|
|
345
|
-
|
|
346
|
-
if (self.pending.crit_probablity != 0) {
|
|
347
|
-
try self.probability.update(self.pending.crit_probablity, 256);
|
|
348
|
-
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
349
|
-
}
|
|
405
|
+
if (self.pending.crit_probablity != 0) {
|
|
406
|
+
try self.probability.update(self.pending.crit_probablity, 256);
|
|
407
|
+
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
408
|
+
} else assert(!self.pending.glitch);
|
|
350
409
|
|
|
410
|
+
if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
|
|
351
411
|
try self.probability.update(1, 39);
|
|
352
412
|
action.damage = self.pending.damage_roll;
|
|
353
413
|
}
|
|
354
414
|
}
|
|
355
415
|
|
|
356
|
-
pub fn
|
|
416
|
+
pub fn glitch(self: *Self) void {
|
|
357
417
|
if (!enabled) return;
|
|
358
418
|
|
|
359
|
-
|
|
419
|
+
assert(!showdown);
|
|
420
|
+
self.pending.glitch = true;
|
|
360
421
|
}
|
|
361
422
|
|
|
362
|
-
pub fn
|
|
423
|
+
pub fn clearPending(self: *Self) void {
|
|
363
424
|
if (!enabled) return;
|
|
364
425
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (status) action.durations.sleep = 0;
|
|
368
|
-
action.durations.confusion = 0;
|
|
369
|
-
action.durations.disable = 0;
|
|
370
|
-
} else {
|
|
371
|
-
action.durations.attacking = 0;
|
|
372
|
-
action.durations.binding = 0;
|
|
373
|
-
}
|
|
426
|
+
assert(!showdown);
|
|
427
|
+
self.pending = .{};
|
|
374
428
|
}
|
|
375
429
|
|
|
376
|
-
pub fn switched(self: *Self, player: Player,
|
|
430
|
+
pub fn switched(self: *Self, player: Player, slot: u8) void {
|
|
377
431
|
if (!enabled) return;
|
|
378
432
|
|
|
379
|
-
assert(
|
|
380
|
-
assert(out >= 1 and out <= 6);
|
|
433
|
+
assert(slot >= 1 and slot <= 6);
|
|
381
434
|
|
|
382
|
-
var
|
|
435
|
+
var d = self.durations.get(player);
|
|
383
436
|
|
|
384
|
-
const
|
|
385
|
-
|
|
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);
|
|
386
440
|
|
|
387
|
-
|
|
388
|
-
|
|
441
|
+
d.confusion = 0;
|
|
442
|
+
d.disable = 0;
|
|
443
|
+
d.attacking = 0;
|
|
444
|
+
d.binding = 0;
|
|
389
445
|
|
|
390
|
-
self.
|
|
446
|
+
self.durations.get(player.foe()).binding = 0;
|
|
391
447
|
}
|
|
392
448
|
|
|
393
449
|
pub fn speedTie(self: *Self, p1: bool) Error!void {
|
|
@@ -398,12 +454,17 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
398
454
|
self.actions.p2.speed_tie = self.actions.p1.speed_tie;
|
|
399
455
|
}
|
|
400
456
|
|
|
401
|
-
pub fn hit(self: *Self, ok: bool, accuracy:
|
|
457
|
+
pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
402
458
|
if (!enabled) return;
|
|
403
459
|
|
|
404
460
|
const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
|
|
405
|
-
|
|
406
|
-
|
|
461
|
+
if (showdown) {
|
|
462
|
+
try self.probability.update(p, 256);
|
|
463
|
+
self.actions.get(player).hit = if (ok) .true else .false;
|
|
464
|
+
} else {
|
|
465
|
+
self.pending.hit = ok;
|
|
466
|
+
self.pending.hit_probablity = p;
|
|
467
|
+
}
|
|
407
468
|
}
|
|
408
469
|
|
|
409
470
|
pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -481,108 +542,172 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
481
542
|
self.actions.get(player).multi_hit = n;
|
|
482
543
|
}
|
|
483
544
|
|
|
484
|
-
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(
|
|
485
552
|
self: *Self,
|
|
486
|
-
comptime field:
|
|
553
|
+
comptime field: Action.Field,
|
|
487
554
|
player: Player,
|
|
488
|
-
|
|
489
|
-
turns: u4,
|
|
555
|
+
obs: Optional(Confusion),
|
|
490
556
|
) void {
|
|
491
557
|
if (!enabled) return;
|
|
492
558
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
(
|
|
499
|
-
|
|
500
|
-
|
|
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,
|
|
501
594
|
}
|
|
502
595
|
}
|
|
503
596
|
|
|
504
|
-
pub fn sleep(self: *Self, player: Player,
|
|
597
|
+
pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
505
598
|
if (!enabled) return;
|
|
506
599
|
|
|
507
|
-
var
|
|
508
|
-
|
|
509
|
-
|
|
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) {
|
|
510
610
|
assert(n >= 1 and n <= 7);
|
|
511
611
|
if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
|
|
512
|
-
|
|
612
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, 0);
|
|
513
613
|
} else {
|
|
614
|
+
assert(obs == .continuing);
|
|
514
615
|
assert(n >= 1 and n < 7);
|
|
515
616
|
try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
|
|
516
|
-
|
|
617
|
+
d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
|
|
517
618
|
}
|
|
518
619
|
}
|
|
519
620
|
|
|
520
|
-
pub fn confusion(self: *Self, player: Player,
|
|
621
|
+
pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
|
|
521
622
|
if (!enabled) return;
|
|
522
623
|
|
|
523
|
-
var
|
|
524
|
-
|
|
525
|
-
|
|
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) {
|
|
526
632
|
assert(n >= 2 and n <= 5);
|
|
527
633
|
if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
|
|
528
|
-
|
|
634
|
+
d.confusion = 0;
|
|
529
635
|
} else {
|
|
636
|
+
assert(obs == .continuing);
|
|
530
637
|
assert(n >= 1 and n < 5);
|
|
531
638
|
if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
|
|
532
|
-
|
|
639
|
+
d.confusion += 1;
|
|
533
640
|
}
|
|
534
641
|
}
|
|
535
642
|
|
|
536
|
-
pub fn disable(self: *Self, player: Player,
|
|
643
|
+
pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
537
644
|
if (!enabled) return;
|
|
538
645
|
|
|
539
|
-
var
|
|
540
|
-
|
|
541
|
-
|
|
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) {
|
|
542
654
|
assert(n >= 1 and n <= 8);
|
|
543
655
|
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
544
|
-
|
|
656
|
+
d.disable = 0;
|
|
545
657
|
} else {
|
|
658
|
+
assert(obs == .continuing);
|
|
546
659
|
assert(n >= 1 and n < 8);
|
|
547
660
|
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
548
|
-
|
|
661
|
+
d.disable += 1;
|
|
549
662
|
}
|
|
550
663
|
}
|
|
551
664
|
|
|
552
|
-
pub fn attacking(self: *Self, player: Player,
|
|
665
|
+
pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
553
666
|
if (!enabled) return;
|
|
554
667
|
|
|
555
|
-
var
|
|
556
|
-
|
|
557
|
-
|
|
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
|
|
558
676
|
assert(n >= 2 and n <= 3);
|
|
559
677
|
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
560
|
-
|
|
678
|
+
d.attacking = 0;
|
|
561
679
|
} else {
|
|
680
|
+
assert(obs == .continuing);
|
|
562
681
|
assert(n >= 1 and n < 3);
|
|
563
682
|
if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
|
|
564
|
-
|
|
683
|
+
d.attacking += 1;
|
|
565
684
|
}
|
|
566
685
|
}
|
|
567
686
|
|
|
568
|
-
pub fn binding(self: *Self, player: Player,
|
|
687
|
+
pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
|
|
569
688
|
if (!enabled) return;
|
|
570
689
|
|
|
571
|
-
var
|
|
572
|
-
|
|
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;
|
|
573
696
|
|
|
574
697
|
assert(n > 0);
|
|
575
698
|
const p: u4 = if (n < 3) 3 else 1;
|
|
576
699
|
const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
|
|
577
700
|
|
|
578
|
-
if (
|
|
701
|
+
if (obs == .ended) {
|
|
702
|
+
// "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
579
703
|
assert(n >= 1 and n <= 4);
|
|
580
704
|
if (n != 4) try self.probability.update(p, q);
|
|
581
|
-
|
|
705
|
+
d.binding = 0;
|
|
582
706
|
} else {
|
|
707
|
+
assert(obs == .continuing);
|
|
583
708
|
assert(n >= 1 and n < 4);
|
|
584
709
|
try self.probability.update(q - p, q);
|
|
585
|
-
|
|
710
|
+
d.binding += 1;
|
|
586
711
|
}
|
|
587
712
|
}
|
|
588
713
|
|
|
@@ -599,6 +724,15 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
599
724
|
try self.probability.update(1, Move.METRONOME.len);
|
|
600
725
|
self.actions.get(player).metronome = move;
|
|
601
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
|
+
}
|
|
602
736
|
};
|
|
603
737
|
}
|
|
604
738
|
|
|
@@ -621,19 +755,25 @@ test "Chance.speedTie" {
|
|
|
621
755
|
test "Chance.hit" {
|
|
622
756
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
623
757
|
|
|
624
|
-
chance.hit(true, 229);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
758
|
+
try chance.hit(.P1, true, 229);
|
|
759
|
+
if (!showdown) {
|
|
760
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
761
|
+
try expectValue(Optional(bool).None, chance.actions.p1.hit);
|
|
762
|
+
|
|
763
|
+
try chance.commit(.P1, .hit);
|
|
764
|
+
}
|
|
628
765
|
try expectValue(Optional(bool).true, chance.actions.p1.hit);
|
|
629
766
|
try expectProbability(&chance.probability, 229, 256);
|
|
630
767
|
|
|
631
768
|
chance.reset();
|
|
632
769
|
|
|
633
|
-
chance.hit(false, 255);
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
770
|
+
try chance.hit(.P2, false, 255);
|
|
771
|
+
if (!showdown) {
|
|
772
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
773
|
+
try expectValue(Optional(bool).None, chance.actions.p2.hit);
|
|
774
|
+
|
|
775
|
+
try chance.commit(.P2, .miss);
|
|
776
|
+
}
|
|
637
777
|
try expectValue(Optional(bool).false, chance.actions.p2.hit);
|
|
638
778
|
try expectProbability(&chance.probability, 1, 256);
|
|
639
779
|
}
|
|
@@ -649,26 +789,18 @@ test "Chance.criticalHit" {
|
|
|
649
789
|
try expectProbability(&chance.probability, 1, 1);
|
|
650
790
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
651
791
|
|
|
652
|
-
try chance.
|
|
653
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
654
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
655
|
-
|
|
656
|
-
chance.hit(true, 128);
|
|
792
|
+
try chance.hit(.P1, true, 128);
|
|
657
793
|
|
|
658
|
-
try chance.
|
|
659
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
794
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
660
795
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
661
796
|
|
|
662
|
-
chance.probability.reset();
|
|
663
797
|
try chance.damage(.P1, 217);
|
|
664
798
|
|
|
665
|
-
try chance.
|
|
666
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
799
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
667
800
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
668
801
|
|
|
669
|
-
chance.probability.reset();
|
|
670
|
-
|
|
671
802
|
try chance.commit(.P1, .hit);
|
|
803
|
+
|
|
672
804
|
try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
|
|
673
805
|
try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
|
|
674
806
|
}
|
|
@@ -683,20 +815,14 @@ test "Chance.criticalHit" {
|
|
|
683
815
|
try expectProbability(&chance.probability, 1, 1);
|
|
684
816
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
685
817
|
|
|
686
|
-
try chance.
|
|
687
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
688
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
689
|
-
|
|
690
|
-
chance.hit(true, 128);
|
|
818
|
+
try chance.hit(.P1, true, 128);
|
|
691
819
|
|
|
692
|
-
try chance.
|
|
693
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
820
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
694
821
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
695
822
|
|
|
696
|
-
chance.probability.reset();
|
|
697
823
|
try chance.damage(.P1, 217);
|
|
698
|
-
|
|
699
824
|
try chance.commit(.P1, .hit);
|
|
825
|
+
|
|
700
826
|
try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
|
|
701
827
|
try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
|
|
702
828
|
}
|
|
@@ -707,19 +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);
|
|
715
|
-
|
|
716
|
-
try chance.commit(.P1, .miss);
|
|
717
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
718
|
-
try expectValue(0, chance.actions.p1.damage);
|
|
840
|
+
try expectValue(@as(u8, 0), chance.actions.p1.damage);
|
|
719
841
|
|
|
720
842
|
try chance.commit(.P1, .hit);
|
|
843
|
+
|
|
721
844
|
try expectProbability(&chance.probability, 1, 39);
|
|
722
|
-
try expectValue(217, chance.actions.p1.damage);
|
|
845
|
+
try expectValue(@as(u8, 217), chance.actions.p1.damage);
|
|
723
846
|
}
|
|
724
847
|
}
|
|
725
848
|
|
|
@@ -771,13 +894,13 @@ test "Chance.moveSlot" {
|
|
|
771
894
|
|
|
772
895
|
try chance.moveSlot(.P2, 2, &ms, 2);
|
|
773
896
|
try expectProbability(&chance.probability, 1, 2);
|
|
774
|
-
try expectValue(2, chance.actions.p2.move_slot);
|
|
897
|
+
try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
|
|
775
898
|
|
|
776
899
|
chance.reset();
|
|
777
900
|
|
|
778
901
|
try chance.moveSlot(.P1, 1, &ms, 0);
|
|
779
902
|
try expectProbability(&chance.probability, 1, 3);
|
|
780
|
-
try expectValue(1, chance.actions.p1.move_slot);
|
|
903
|
+
try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
|
|
781
904
|
}
|
|
782
905
|
|
|
783
906
|
test "Chance.multiHit" {
|
|
@@ -785,50 +908,43 @@ test "Chance.multiHit" {
|
|
|
785
908
|
|
|
786
909
|
try chance.multiHit(.P1, 3);
|
|
787
910
|
try expectProbability(&chance.probability, 3, 8);
|
|
788
|
-
try expectValue(3, chance.actions.p1.multi_hit);
|
|
911
|
+
try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
|
|
789
912
|
|
|
790
913
|
chance.reset();
|
|
791
914
|
|
|
792
915
|
try chance.multiHit(.P2, 5);
|
|
793
916
|
try expectProbability(&chance.probability, 1, 8);
|
|
794
|
-
try expectValue(5, chance.actions.p2.multi_hit);
|
|
917
|
+
try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
|
|
795
918
|
}
|
|
796
919
|
|
|
797
920
|
test "Chance.duration" {
|
|
798
921
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
799
922
|
|
|
800
|
-
chance.duration(.
|
|
801
|
-
try expectValue(2, chance.actions.p1.duration);
|
|
802
|
-
try expectValue(1, chance.actions.p2.durations.sleep);
|
|
923
|
+
chance.duration(.P1, 2);
|
|
924
|
+
try expectValue(@as(u4, 2), chance.actions.p1.duration);
|
|
803
925
|
|
|
804
926
|
chance.reset();
|
|
805
927
|
|
|
806
|
-
chance.duration(.
|
|
807
|
-
|
|
808
|
-
try expectValue(0, chance.actions.p2.duration);
|
|
809
|
-
try expectValue(0, chance.actions.p2.durations.binding);
|
|
810
|
-
try chance.commit(.P2, .hit);
|
|
811
|
-
}
|
|
812
|
-
try expectValue(4, chance.actions.p2.duration);
|
|
813
|
-
try expectValue(1, chance.actions.p2.durations.binding);
|
|
928
|
+
chance.duration(.P2, 4);
|
|
929
|
+
try expectValue(@as(u4, 4), chance.actions.p2.duration);
|
|
814
930
|
}
|
|
815
931
|
|
|
816
932
|
test "Chance.sleep" {
|
|
817
933
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
818
934
|
|
|
819
935
|
for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
|
|
820
|
-
chance.
|
|
821
|
-
try chance.sleep(.P1,
|
|
936
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
937
|
+
try chance.sleep(.P1, .ended);
|
|
822
938
|
try expectProbability(&chance.probability, 1, d);
|
|
823
|
-
try expectValue(0, chance.
|
|
939
|
+
try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
824
940
|
|
|
825
941
|
chance.reset();
|
|
826
942
|
|
|
827
943
|
if (i < 7) {
|
|
828
|
-
chance.
|
|
829
|
-
try chance.sleep(.P1,
|
|
944
|
+
chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
|
|
945
|
+
try chance.sleep(.P1, .continuing);
|
|
830
946
|
try expectProbability(&chance.probability, d - 1, d);
|
|
831
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
947
|
+
try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
|
|
832
948
|
|
|
833
949
|
chance.reset();
|
|
834
950
|
}
|
|
@@ -840,19 +956,19 @@ test "Chance.confusion" {
|
|
|
840
956
|
|
|
841
957
|
for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
|
|
842
958
|
if (i > 1) {
|
|
843
|
-
chance.
|
|
844
|
-
try chance.confusion(.P2,
|
|
959
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
960
|
+
try chance.confusion(.P2, .ended);
|
|
845
961
|
try expectProbability(&chance.probability, 1, d);
|
|
846
|
-
try expectValue(0, chance.
|
|
962
|
+
try expectValue(@as(u3, 0), chance.durations.p2.confusion);
|
|
847
963
|
|
|
848
964
|
chance.reset();
|
|
849
965
|
}
|
|
850
966
|
|
|
851
967
|
if (i < 5) {
|
|
852
|
-
chance.
|
|
853
|
-
try chance.confusion(.P2,
|
|
968
|
+
chance.durations.p2.confusion = @intCast(i);
|
|
969
|
+
try chance.confusion(.P2, .continuing);
|
|
854
970
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
855
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
971
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
|
|
856
972
|
|
|
857
973
|
chance.reset();
|
|
858
974
|
}
|
|
@@ -863,18 +979,18 @@ test "Chance.disable" {
|
|
|
863
979
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
864
980
|
|
|
865
981
|
for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
866
|
-
chance.
|
|
867
|
-
try chance.disable(.P1,
|
|
982
|
+
chance.durations.p1.disable = @intCast(i);
|
|
983
|
+
try chance.disable(.P1, .ended);
|
|
868
984
|
try expectProbability(&chance.probability, 1, d);
|
|
869
|
-
try expectValue(0, chance.
|
|
985
|
+
try expectValue(@as(u4, 0), chance.durations.p1.disable);
|
|
870
986
|
|
|
871
987
|
chance.reset();
|
|
872
988
|
|
|
873
989
|
if (i < 8) {
|
|
874
|
-
chance.
|
|
875
|
-
try chance.disable(.P1,
|
|
990
|
+
chance.durations.p1.disable = @intCast(i);
|
|
991
|
+
try chance.disable(.P1, .continuing);
|
|
876
992
|
try expectProbability(&chance.probability, d - 1, d);
|
|
877
|
-
try expectValue(@as(u4, @intCast(i)) + 1, chance.
|
|
993
|
+
try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
|
|
878
994
|
|
|
879
995
|
chance.reset();
|
|
880
996
|
}
|
|
@@ -886,19 +1002,19 @@ test "Chance.attacking" {
|
|
|
886
1002
|
|
|
887
1003
|
for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
|
|
888
1004
|
if (i > 1) {
|
|
889
|
-
chance.
|
|
890
|
-
try chance.attacking(.P2,
|
|
1005
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1006
|
+
try chance.attacking(.P2, .ended);
|
|
891
1007
|
try expectProbability(&chance.probability, 1, d);
|
|
892
|
-
try expectValue(0, chance.
|
|
1008
|
+
try expectValue(@as(u3, 0), chance.durations.p2.attacking);
|
|
893
1009
|
|
|
894
1010
|
chance.reset();
|
|
895
1011
|
}
|
|
896
1012
|
|
|
897
1013
|
if (i < 3) {
|
|
898
|
-
chance.
|
|
899
|
-
try chance.attacking(.P2,
|
|
1014
|
+
chance.durations.p2.attacking = @intCast(i);
|
|
1015
|
+
try chance.attacking(.P2, .continuing);
|
|
900
1016
|
try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
|
|
901
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1017
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
|
|
902
1018
|
|
|
903
1019
|
chance.reset();
|
|
904
1020
|
}
|
|
@@ -912,18 +1028,18 @@ test "Chance.binding" {
|
|
|
912
1028
|
const qs = [_]u8{ 8, 5, 2, 1 };
|
|
913
1029
|
|
|
914
1030
|
for (ps, qs, 1..5) |p, q, i| {
|
|
915
|
-
chance.
|
|
916
|
-
try chance.binding(.P1,
|
|
1031
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1032
|
+
try chance.binding(.P1, .ended);
|
|
917
1033
|
try expectProbability(&chance.probability, p, q);
|
|
918
|
-
try expectValue(0, chance.
|
|
1034
|
+
try expectValue(@as(u3, 0), chance.durations.p1.binding);
|
|
919
1035
|
|
|
920
1036
|
chance.reset();
|
|
921
1037
|
|
|
922
1038
|
if (i < 4) {
|
|
923
|
-
chance.
|
|
924
|
-
try chance.binding(.P1,
|
|
1039
|
+
chance.durations.p1.binding = @intCast(i);
|
|
1040
|
+
try chance.binding(.P1, .continuing);
|
|
925
1041
|
try expectProbability(&chance.probability, q - p, q);
|
|
926
|
-
try expectValue(@as(u3, @intCast(i)) + 1, chance.
|
|
1042
|
+
try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
|
|
927
1043
|
|
|
928
1044
|
chance.reset();
|
|
929
1045
|
}
|
|
@@ -935,7 +1051,7 @@ test "Chance.psywave" {
|
|
|
935
1051
|
|
|
936
1052
|
try chance.psywave(.P2, 100, 150);
|
|
937
1053
|
try expectProbability(&chance.probability, 1, 150);
|
|
938
|
-
try expectValue(101, chance.actions.p2.psywave);
|
|
1054
|
+
try expectValue(@as(u8, 101), chance.actions.p2.psywave);
|
|
939
1055
|
}
|
|
940
1056
|
|
|
941
1057
|
test "Chance.metronome" {
|
|
@@ -973,24 +1089,24 @@ const Null = struct {
|
|
|
973
1089
|
_ = .{ self, player, kind };
|
|
974
1090
|
}
|
|
975
1091
|
|
|
976
|
-
pub fn
|
|
1092
|
+
pub fn glitch(self: Null) void {
|
|
977
1093
|
_ = .{self};
|
|
978
1094
|
}
|
|
979
1095
|
|
|
980
|
-
pub fn
|
|
981
|
-
_ = .{
|
|
1096
|
+
pub fn clearPending(self: Null) void {
|
|
1097
|
+
_ = .{self};
|
|
982
1098
|
}
|
|
983
1099
|
|
|
984
|
-
pub fn switched(self: Null, player: Player,
|
|
985
|
-
_ = .{ self, player,
|
|
1100
|
+
pub fn switched(self: Null, player: Player, slot: u8) void {
|
|
1101
|
+
_ = .{ self, player, slot };
|
|
986
1102
|
}
|
|
987
1103
|
|
|
988
1104
|
pub fn speedTie(self: Null, p1: bool) Error!void {
|
|
989
1105
|
_ = .{ self, p1 };
|
|
990
1106
|
}
|
|
991
1107
|
|
|
992
|
-
pub fn hit(self: Null, ok: bool, accuracy:
|
|
993
|
-
_ = .{ self, ok, accuracy };
|
|
1108
|
+
pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
1109
|
+
_ = .{ self, player, ok, accuracy };
|
|
994
1110
|
}
|
|
995
1111
|
|
|
996
1112
|
pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -1021,34 +1137,37 @@ const Null = struct {
|
|
|
1021
1137
|
_ = .{ self, player, n };
|
|
1022
1138
|
}
|
|
1023
1139
|
|
|
1024
|
-
pub fn duration(
|
|
1140
|
+
pub fn duration(self: Null, player: Player, turns: u4) void {
|
|
1141
|
+
_ = .{ self, player, turns };
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
pub fn observe(
|
|
1025
1145
|
self: Null,
|
|
1026
|
-
comptime field:
|
|
1146
|
+
comptime field: Action.Field,
|
|
1027
1147
|
player: Player,
|
|
1028
|
-
|
|
1029
|
-
turns: u4,
|
|
1148
|
+
obs: Optional(Confusion),
|
|
1030
1149
|
) void {
|
|
1031
|
-
_ = .{ self, field, player,
|
|
1150
|
+
_ = .{ self, field, player, obs };
|
|
1032
1151
|
}
|
|
1033
1152
|
|
|
1034
|
-
pub fn sleep(self: Null, player: Player,
|
|
1035
|
-
_ = .{ self, player,
|
|
1153
|
+
pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1154
|
+
_ = .{ self, player, obs };
|
|
1036
1155
|
}
|
|
1037
1156
|
|
|
1038
|
-
pub fn
|
|
1039
|
-
_ = .{ self, player,
|
|
1157
|
+
pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
|
|
1158
|
+
_ = .{ self, player, obs };
|
|
1040
1159
|
}
|
|
1041
1160
|
|
|
1042
|
-
pub fn
|
|
1043
|
-
_ = .{ self, player,
|
|
1161
|
+
pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1162
|
+
_ = .{ self, player, obs };
|
|
1044
1163
|
}
|
|
1045
1164
|
|
|
1046
|
-
pub fn attacking(self: Null, player: Player,
|
|
1047
|
-
_ = .{ self, player,
|
|
1165
|
+
pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1166
|
+
_ = .{ self, player, obs };
|
|
1048
1167
|
}
|
|
1049
1168
|
|
|
1050
|
-
pub fn binding(self: Null, player: Player,
|
|
1051
|
-
_ = .{ self, player,
|
|
1169
|
+
pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
|
|
1170
|
+
_ = .{ self, player, obs };
|
|
1052
1171
|
}
|
|
1053
1172
|
|
|
1054
1173
|
pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
|
|
@@ -1058,4 +1177,9 @@ const Null = struct {
|
|
|
1058
1177
|
pub fn metronome(self: Null, player: Player, move: Move) Error!void {
|
|
1059
1178
|
_ = .{ self, player, move };
|
|
1060
1179
|
}
|
|
1180
|
+
|
|
1181
|
+
pub fn pp(self: Null, player: Player, n: u4) bool {
|
|
1182
|
+
_ = .{ self, player, n };
|
|
1183
|
+
return true;
|
|
1184
|
+
}
|
|
1061
1185
|
};
|