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