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