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