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