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