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