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