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