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