@pkmn/engine 0.1.0-dev.dc33f678 → 0.1.0-dev.deda8593
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 +44 -19
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +5 -5
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +100 -102
- package/package.json +33 -22
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +397 -384
- package/src/lib/common/rational.zig +11 -8
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +217 -47
- package/src/lib/gen1/chance.zig +91 -92
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +3 -3
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +35 -35
- package/src/lib/gen1/helpers.zig +136 -55
- package/src/lib/gen1/mechanics.zig +333 -312
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1315 -0
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +5 -5
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +10 -10
- package/src/lib/gen2/data.zig +27 -37
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +1621 -456
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
|
@@ -43,8 +43,7 @@ pub fn Rational(comptime T: type) type {
|
|
|
43
43
|
/// Update the rational by multiplying its numerator by p and its denominator by q.
|
|
44
44
|
/// Both p and q must be >= 1, and if computable at comptime must have no common factors.
|
|
45
45
|
pub fn update(r: *Self, p: anytype, q: anytype) Error!void {
|
|
46
|
-
//
|
|
47
|
-
// std.debug.print("({d}/{d}) * ", .{p, q});
|
|
46
|
+
// std.debug.print("({d}/{d}) * ", .{ p, q }); // DEBUG
|
|
48
47
|
assert(p >= 1);
|
|
49
48
|
assert(q >= 1);
|
|
50
49
|
|
|
@@ -185,16 +184,16 @@ pub fn Rational(comptime T: type) type {
|
|
|
185
184
|
try writer.print("{d}/{d}", .{ self.p, self.q });
|
|
186
185
|
}
|
|
187
186
|
|
|
188
|
-
|
|
187
|
+
fn multiplication(r: *Self, p: anytype, q: anytype) !void {
|
|
189
188
|
r.p = try std.math.mul(T, r.p, p);
|
|
190
189
|
r.q = try std.math.mul(T, r.q, q);
|
|
191
190
|
}
|
|
192
191
|
|
|
193
|
-
|
|
192
|
+
fn addition(r: *Self, p: anytype, q: anytype) !void {
|
|
194
193
|
// (a/b) + (c/d) = (ad+bc)/(bd)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
const d = try std.math.mul(T, r.q, q);
|
|
195
|
+
const n1 = try std.math.mul(T, r.p, q);
|
|
196
|
+
const n2 = try std.math.mul(T, r.q, p);
|
|
198
197
|
|
|
199
198
|
r.p = try std.math.add(T, n1, n2);
|
|
200
199
|
r.q = d;
|
|
@@ -203,7 +202,7 @@ pub fn Rational(comptime T: type) type {
|
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
// https://en.wikipedia.org/wiki/Euclidean_algorithm
|
|
206
|
-
|
|
205
|
+
fn gcd(p: anytype, q: anytype) @TypeOf(p, q) {
|
|
207
206
|
assert(p >= 1);
|
|
208
207
|
assert(q >= 1);
|
|
209
208
|
|
|
@@ -234,6 +233,8 @@ test Rational {
|
|
|
234
233
|
var r: Rational(t) = .{};
|
|
235
234
|
|
|
236
235
|
var c: t = 128;
|
|
236
|
+
_ = &c;
|
|
237
|
+
|
|
237
238
|
try r.update(c, 256);
|
|
238
239
|
try doTurn(&r);
|
|
239
240
|
|
|
@@ -320,6 +321,8 @@ test BigRational {
|
|
|
320
321
|
try r.reset();
|
|
321
322
|
|
|
322
323
|
var c: u8 = 128;
|
|
324
|
+
_ = &c;
|
|
325
|
+
|
|
323
326
|
try r.update(c, 256);
|
|
324
327
|
try doTurn(&r);
|
|
325
328
|
|
package/src/lib/common/rng.zig
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
2
|
|
|
3
|
-
const options = @import("
|
|
3
|
+
const options = @import("options.zig");
|
|
4
4
|
|
|
5
5
|
const assert = std.debug.assert;
|
|
6
6
|
const expectEqual = std.testing.expectEqual;
|
|
@@ -25,6 +25,11 @@ pub fn PRNG(comptime gen: comptime_int) type {
|
|
|
25
25
|
pub fn next(self: *Self) Output(gen) {
|
|
26
26
|
return self.src.next();
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
pub fn advance(self: *Self, n: usize) void {
|
|
30
|
+
if (!options.advance) return;
|
|
31
|
+
for (0..n) |_| self.src.advance();
|
|
32
|
+
}
|
|
28
33
|
};
|
|
29
34
|
}
|
|
30
35
|
|
|
@@ -61,11 +66,15 @@ pub const PSRNG = extern struct {
|
|
|
61
66
|
(@as(u64, self.range(u16, 0, 0x10000)) << 16) |
|
|
62
67
|
(@as(u64, self.range(u16, 0, 0x10000)));
|
|
63
68
|
}
|
|
69
|
+
|
|
70
|
+
pub fn Bound(comptime T: type) type {
|
|
71
|
+
return std.math.IntFittingRange(0, std.math.maxInt(T) + 1);
|
|
72
|
+
}
|
|
64
73
|
};
|
|
65
74
|
|
|
66
75
|
test PSRNG {
|
|
67
76
|
var psrng: PSRNG = .{ .src = .{ .seed = 0x0001000200030004 } };
|
|
68
|
-
try expectEqual(
|
|
77
|
+
try expectEqual(121, psrng.range(u8, 0, 256));
|
|
69
78
|
try expectEqual(false, psrng.chance(u8, 128, 256)); // 226 < 128
|
|
70
79
|
}
|
|
71
80
|
|
|
@@ -89,6 +98,10 @@ pub const Gen12 = extern struct {
|
|
|
89
98
|
self.index = (self.index + 1) % 9;
|
|
90
99
|
return val;
|
|
91
100
|
}
|
|
101
|
+
|
|
102
|
+
pub fn advance(self: *Gen12) void {
|
|
103
|
+
_ = self.next();
|
|
104
|
+
}
|
|
92
105
|
};
|
|
93
106
|
|
|
94
107
|
test Gen12 {
|
|
@@ -98,9 +111,9 @@ test Gen12 {
|
|
|
98
111
|
try expectEqual(e, rng.next());
|
|
99
112
|
}
|
|
100
113
|
|
|
101
|
-
try expectEqual(
|
|
102
|
-
try expectEqual(
|
|
103
|
-
try expectEqual(
|
|
114
|
+
try expectEqual(16, Gen12.percent(6) + 1);
|
|
115
|
+
try expectEqual(16, Gen12.percent(7) - 1);
|
|
116
|
+
try expectEqual(128, Gen12.percent(50) + 1);
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
// https://pkmn.cc/pokeemerald/src/random.c
|
|
@@ -188,7 +201,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
|
|
|
188
201
|
for (0..n) |_| _ = self.next();
|
|
189
202
|
}
|
|
190
203
|
|
|
191
|
-
pub fn range(self: *Self, comptime T: type, from: T, to: Bound(T)) T {
|
|
204
|
+
pub fn range(self: *Self, comptime T: type, from: T, to: PSRNG.Bound(T)) T {
|
|
192
205
|
assert(showdown);
|
|
193
206
|
return @intCast(@as(u64, self.next()) * (to - from) / divisor + from);
|
|
194
207
|
}
|
|
@@ -197,7 +210,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
|
|
|
197
210
|
self: *Self,
|
|
198
211
|
comptime T: type,
|
|
199
212
|
numerator: T,
|
|
200
|
-
denominator: Bound(T),
|
|
213
|
+
denominator: PSRNG.Bound(T),
|
|
201
214
|
) bool {
|
|
202
215
|
assert(showdown);
|
|
203
216
|
assert(denominator > 0);
|
|
@@ -228,7 +241,3 @@ fn Output(comptime gen: comptime_int) type {
|
|
|
228
241
|
else => unreachable,
|
|
229
242
|
};
|
|
230
243
|
}
|
|
231
|
-
|
|
232
|
-
fn Bound(comptime T: type) type {
|
|
233
|
-
return std.math.IntFittingRange(0, std.math.maxInt(T) + 1);
|
|
234
|
-
}
|
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -38,6 +38,9 @@ const Duration = chance.Duration;
|
|
|
38
38
|
|
|
39
39
|
const Rolls = helpers.Rolls;
|
|
40
40
|
|
|
41
|
+
const tty = true; // DEBUG
|
|
42
|
+
const summary = false; // DEBUG
|
|
43
|
+
|
|
41
44
|
/// Information relevant to damage calculation that occured during a Generation I battle `update`.
|
|
42
45
|
pub const Summaries = extern struct {
|
|
43
46
|
/// Relevant information for Player 1.
|
|
@@ -50,7 +53,7 @@ pub const Summaries = extern struct {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
/// Returns the `Summary` for the given `player`.
|
|
53
|
-
pub
|
|
56
|
+
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
|
|
54
57
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Summaries);
|
|
55
58
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
56
59
|
}
|
|
@@ -69,8 +72,10 @@ pub const Summary = extern struct {
|
|
|
69
72
|
/// The final computed damage that gets applied to the Pokémon. May exceed the target's HP
|
|
70
73
|
// (to determine the *actual* damage done compare the target's stored HP before and after).
|
|
71
74
|
final: u16 = 0,
|
|
72
|
-
/// Whether higher damage will saturate / result in the same outcome (e.g. additional
|
|
73
|
-
/// gets ignored due to it already breaking a Substitute or causing the target
|
|
75
|
+
/// Whether higher direct damage will saturate / result in the same outcome (e.g. additional
|
|
76
|
+
/// direct damage gets ignored due to it already breaking a Substitute or causing the target
|
|
77
|
+
/// to faint). Note that this field does not get set in scenarios where the target would
|
|
78
|
+
/// only be guaranteed to faint due to some sort of subsequent recoil or residual damage.
|
|
74
79
|
capped: bool = false,
|
|
75
80
|
|
|
76
81
|
// NOTE: 15 bits padding
|
|
@@ -201,6 +206,8 @@ pub const Options = struct {
|
|
|
201
206
|
metronome: bool = false,
|
|
202
207
|
};
|
|
203
208
|
|
|
209
|
+
pub const MAX_FRONTIER = 19;
|
|
210
|
+
|
|
204
211
|
pub fn transitions(
|
|
205
212
|
battle: anytype,
|
|
206
213
|
c1: Choice,
|
|
@@ -211,8 +218,8 @@ pub fn transitions(
|
|
|
211
218
|
) !?Stats {
|
|
212
219
|
var stats: Stats = .{};
|
|
213
220
|
|
|
214
|
-
|
|
215
|
-
|
|
221
|
+
const actions = options.actions;
|
|
222
|
+
const cap = options.cap;
|
|
216
223
|
|
|
217
224
|
var seen = std.AutoHashMap(Actions, void).init(allocator);
|
|
218
225
|
defer seen.deinit();
|
|
@@ -232,8 +239,8 @@ pub fn transitions(
|
|
|
232
239
|
_ = try b.update(c1, c2, &opts);
|
|
233
240
|
stats.updates += 1;
|
|
234
241
|
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
const p1 = b.side(.P1);
|
|
243
|
+
const p2 = b.side(.P2);
|
|
237
244
|
|
|
238
245
|
var p: Rational(u128) = .{ .p = 0, .q = 1 };
|
|
239
246
|
try frontier.append(opts.chance.actions);
|
|
@@ -247,13 +254,19 @@ pub fn transitions(
|
|
|
247
254
|
var i: usize = 0;
|
|
248
255
|
assert(frontier.items.len == 1);
|
|
249
256
|
while (i < frontier.items.len) : (i += 1) {
|
|
250
|
-
|
|
257
|
+
const template = frontier.items[i];
|
|
251
258
|
opts.calc.overrides.durations = .{
|
|
252
259
|
.p1 = template.p1.durations,
|
|
253
260
|
.p2 = template.p2.durations,
|
|
254
261
|
};
|
|
255
262
|
|
|
256
|
-
try debug(writer, template,
|
|
263
|
+
try debug(writer, template, .{
|
|
264
|
+
.shape = true,
|
|
265
|
+
.color = i,
|
|
266
|
+
.bold = true,
|
|
267
|
+
.background = true,
|
|
268
|
+
.indent = false,
|
|
269
|
+
});
|
|
257
270
|
|
|
258
271
|
var a: Actions = .{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
|
|
259
272
|
|
|
@@ -281,7 +294,7 @@ pub fn transitions(
|
|
|
281
294
|
for (Rolls.moveSlot(template.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
282
295
|
for (Rolls.moveSlot(template.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
283
296
|
for (Rolls.multiHit(template.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
284
|
-
for (Rolls.multiHit(template.p2,
|
|
297
|
+
for (Rolls.multiHit(template.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
285
298
|
for (Rolls.secondaryChance(template.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
286
299
|
for (Rolls.secondaryChance(template.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
287
300
|
for (Rolls.criticalHit(template.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
@@ -290,13 +303,17 @@ pub fn transitions(
|
|
|
290
303
|
var p1_dmg = Rolls.damage(template.p1, p1_hit);
|
|
291
304
|
while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
|
|
292
305
|
a.p1.damage = @intCast(p1_dmg.min);
|
|
293
|
-
var p1_min: u8 = 0;
|
|
294
306
|
|
|
295
307
|
var p2_dmg = Rolls.damage(template.p2, p2_hit);
|
|
308
|
+
|
|
309
|
+
const p1_min: u9 = p1_dmg.min;
|
|
310
|
+
const p2_min: u9 = p2_dmg.min;
|
|
311
|
+
|
|
296
312
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
297
313
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
298
314
|
|
|
299
315
|
opts.calc.overrides.actions = a;
|
|
316
|
+
opts.calc.summaries = .{};
|
|
300
317
|
opts.chance = .{ .probability = .{}, .actions = actions };
|
|
301
318
|
const q = &opts.chance.probability;
|
|
302
319
|
|
|
@@ -304,28 +321,40 @@ pub fn transitions(
|
|
|
304
321
|
_ = try b.update(c1, c2, &opts);
|
|
305
322
|
stats.updates += 1;
|
|
306
323
|
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// try Rolls.coalesce(.P2, @intCast(u8, p2_dmg.min), &opts.calc.summaries, cap);
|
|
324
|
+
const summaries = &opts.calc.summaries;
|
|
325
|
+
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
326
|
+
p1_dmg.min
|
|
327
|
+
else
|
|
328
|
+
try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
329
|
+
const p2_max: u9 =
|
|
330
|
+
try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
315
331
|
|
|
316
332
|
if (opts.chance.actions.matches(template)) {
|
|
317
333
|
if (!opts.chance.actions.eql(a)) {
|
|
318
|
-
|
|
334
|
+
if (!summary) {
|
|
335
|
+
try debug(writer, opts.chance.actions, .{
|
|
336
|
+
.p1_max = p1_max,
|
|
337
|
+
.p2_max = p2_max,
|
|
338
|
+
.color = i,
|
|
339
|
+
.dim = true,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
319
342
|
|
|
320
|
-
|
|
343
|
+
p1_dmg.min = p1_max;
|
|
321
344
|
p2_dmg.min = p2_max;
|
|
322
345
|
continue;
|
|
323
346
|
}
|
|
324
347
|
|
|
325
|
-
|
|
348
|
+
if (!summary) {
|
|
349
|
+
try debug(writer, opts.chance.actions, .{
|
|
350
|
+
.p1_max = p1_max,
|
|
351
|
+
.p2_max = p2_max,
|
|
352
|
+
.color = i,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
326
355
|
|
|
327
|
-
for (
|
|
328
|
-
for (p2_dmg.min
|
|
356
|
+
for (p1_min..p1_max + 1) |p1d| {
|
|
357
|
+
for (p2_dmg.min..p2_max + 1) |p2d| {
|
|
329
358
|
var acts = opts.chance.actions;
|
|
330
359
|
acts.p1.damage = @intCast(p1d);
|
|
331
360
|
acts.p2.damage = @intCast(p2d);
|
|
@@ -336,7 +365,7 @@ pub fn transitions(
|
|
|
336
365
|
}
|
|
337
366
|
}
|
|
338
367
|
|
|
339
|
-
if (p1_max !=
|
|
368
|
+
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
340
369
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
341
370
|
try p.add(q);
|
|
342
371
|
stats.saved += 1;
|
|
@@ -346,27 +375,46 @@ pub fn transitions(
|
|
|
346
375
|
return error.TestUnexpectedResult;
|
|
347
376
|
}
|
|
348
377
|
} else {
|
|
349
|
-
try debug(writer, opts.chance.actions, false, .{ .dim = true });
|
|
350
|
-
|
|
351
378
|
if (!matches(opts.chance.actions, i, frontier.items)) {
|
|
352
379
|
try frontier.append(opts.chance.actions);
|
|
353
380
|
|
|
354
|
-
try debug(writer, opts.chance.actions,
|
|
381
|
+
try debug(writer, opts.chance.actions, .{
|
|
382
|
+
.p1_max = p1_max,
|
|
383
|
+
.p2_max = p2_max,
|
|
384
|
+
.dim = true,
|
|
385
|
+
.newline = false,
|
|
386
|
+
});
|
|
387
|
+
try writer.writeAll(" → ");
|
|
388
|
+
try debug(writer, opts.chance.actions, .{
|
|
389
|
+
.shape = true,
|
|
355
390
|
.color = frontier.items.len - 1,
|
|
356
391
|
.dim = true,
|
|
357
|
-
.background = true
|
|
392
|
+
.background = true,
|
|
393
|
+
.indent = false,
|
|
394
|
+
});
|
|
395
|
+
} else if (!summary) {
|
|
396
|
+
try debug(writer, opts.chance.actions, .{
|
|
397
|
+
.p1_max = p1_max,
|
|
398
|
+
.p2_max = p2_max,
|
|
399
|
+
.dim = true,
|
|
358
400
|
});
|
|
359
401
|
}
|
|
360
402
|
}
|
|
361
403
|
|
|
362
|
-
|
|
404
|
+
p1_dmg.min = p1_max;
|
|
363
405
|
p2_dmg.min = p2_max;
|
|
364
406
|
}
|
|
365
407
|
|
|
366
|
-
assert(p1_min > 0 or p1_dmg.min == 0);
|
|
367
|
-
p1_dmg.min = p1_min;
|
|
368
|
-
|
|
369
408
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
409
|
+
|
|
410
|
+
if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
|
|
411
|
+
p.reduce();
|
|
412
|
+
try writer.print(
|
|
413
|
+
" = {s} ({d:.2}%)\n\n",
|
|
414
|
+
.{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
370
418
|
}
|
|
371
419
|
|
|
372
420
|
assert(frontier.items.len == i);
|
|
@@ -387,7 +435,86 @@ pub fn transitions(
|
|
|
387
435
|
return stats;
|
|
388
436
|
}
|
|
389
437
|
|
|
390
|
-
|
|
438
|
+
pub fn update(
|
|
439
|
+
battle: anytype,
|
|
440
|
+
c1: Choice,
|
|
441
|
+
c2: Choice,
|
|
442
|
+
options: anytype,
|
|
443
|
+
allocator: std.mem.Allocator,
|
|
444
|
+
writer: anytype,
|
|
445
|
+
transition: bool,
|
|
446
|
+
) !Result {
|
|
447
|
+
options.chance.reset();
|
|
448
|
+
|
|
449
|
+
if (!pkmn.options.chance or !pkmn.options.calc or @TypeOf(battle.rng) != data.PRNG) {
|
|
450
|
+
return battle.update(c1, c2, options);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
var copy = battle.*;
|
|
454
|
+
const actions = options.chance.actions;
|
|
455
|
+
|
|
456
|
+
// Perfom the actual update
|
|
457
|
+
const result = battle.update(c1, c2, options);
|
|
458
|
+
|
|
459
|
+
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
460
|
+
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
461
|
+
var stream: protocol.ByteStream = .{ .buffer = &buf };
|
|
462
|
+
const n = try helpers.diff(©, battle, stream.writer());
|
|
463
|
+
|
|
464
|
+
// Applying the diff to the battle should take us back to the original copy
|
|
465
|
+
// of the battle (ignoring the RNG).
|
|
466
|
+
var patched = battle.*;
|
|
467
|
+
patched.rng = copy.rng;
|
|
468
|
+
helpers.patch(&patched, buf[0..n]);
|
|
469
|
+
try expectEqual(copy, patched);
|
|
470
|
+
|
|
471
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return result;
|
|
472
|
+
|
|
473
|
+
if (transition) {
|
|
474
|
+
// Ensure we can generate all transitions from the same original state
|
|
475
|
+
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
476
|
+
// the transitions function relies on RNG for discovery of states)
|
|
477
|
+
if (try transitions(unfix(copy), c1, c2, allocator, writer, .{
|
|
478
|
+
.actions = actions,
|
|
479
|
+
.cap = true,
|
|
480
|
+
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Demonstrate that we can produce the same state by forcing the RNG to behave the
|
|
484
|
+
// same as we observed - note that because we do not pass in a durations override
|
|
485
|
+
// mask none of the durations will be extended.
|
|
486
|
+
var override = pkmn.battle.options(
|
|
487
|
+
protocol.NULL,
|
|
488
|
+
chance.NULL,
|
|
489
|
+
Calc{ .overrides = .{ .actions = actions } },
|
|
490
|
+
);
|
|
491
|
+
const overridden = copy.update(c1, c2, &override);
|
|
492
|
+
try expectEqual(result, overridden);
|
|
493
|
+
|
|
494
|
+
// The actual battle excluding its RNG field should match a copy updated with
|
|
495
|
+
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
496
|
+
copy.rng = battle.rng;
|
|
497
|
+
try expectEqual(copy, battle.*);
|
|
498
|
+
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
503
|
+
return .{
|
|
504
|
+
.sides = actual.sides,
|
|
505
|
+
.turn = actual.turn,
|
|
506
|
+
.last_damage = actual.last_damage,
|
|
507
|
+
.last_moves = actual.last_moves,
|
|
508
|
+
.rng = .{ .src = .{
|
|
509
|
+
.seed = if (pkmn.options.showdown)
|
|
510
|
+
0x12345678
|
|
511
|
+
else
|
|
512
|
+
.{ 123, 234, 56, 78, 9, 101, 112, 131, 4 },
|
|
513
|
+
} },
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
391
518
|
for (frontier, 0..) |f, j| {
|
|
392
519
|
// TODO: is skipping this redundant check worth it?
|
|
393
520
|
if (i == j) continue;
|
|
@@ -404,23 +531,66 @@ fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
|
404
531
|
}
|
|
405
532
|
|
|
406
533
|
const Style = struct {
|
|
534
|
+
shape: bool = false,
|
|
535
|
+
p1_max: u9 = 0,
|
|
536
|
+
p2_max: u9 = 0,
|
|
407
537
|
color: ?usize = null,
|
|
408
538
|
bold: bool = false,
|
|
409
539
|
background: bool = false,
|
|
410
540
|
dim: bool = false,
|
|
541
|
+
newline: bool = true,
|
|
542
|
+
indent: bool = true,
|
|
411
543
|
};
|
|
412
544
|
|
|
413
|
-
fn debug(writer: anytype, actions: Actions,
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
545
|
+
fn debug(writer: anytype, actions: Actions, style: Style) !void {
|
|
546
|
+
if (style.indent) try writer.writeAll(" ");
|
|
547
|
+
if (tty) {
|
|
548
|
+
const mod: usize = if (style.dim) 2 else 1;
|
|
549
|
+
const background: usize = if (style.background) 4 else 3;
|
|
550
|
+
const color: usize = if (style.color) |c| (c % 6) + 1 else 7;
|
|
551
|
+
|
|
552
|
+
if (style.dim or style.bold) try writer.print("\x1b[{d}m", .{mod});
|
|
553
|
+
try writer.print("\x1b[{d}{d}m", .{ background, color });
|
|
554
|
+
const p1 = if (style.p1_max != 0 and style.p1_max != actions.p1.damage) style.p1_max else 0;
|
|
555
|
+
const p2 = if (style.p2_max != 0 and style.p2_max != actions.p2.damage) style.p2_max else 0;
|
|
556
|
+
if (p1 != 0 or p2 != 0) {
|
|
557
|
+
assert(!style.shape);
|
|
558
|
+
try format(writer, actions, p1, p2);
|
|
559
|
+
} else {
|
|
560
|
+
try actions.fmt(writer, style.shape);
|
|
561
|
+
}
|
|
562
|
+
try writer.writeAll("\x1b[0m");
|
|
563
|
+
} else {
|
|
564
|
+
if (style.dim) try writer.writeAll(" ");
|
|
565
|
+
try actions.fmt(writer, style.shape);
|
|
566
|
+
}
|
|
567
|
+
if (style.newline) try writer.writeByte('\n');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
|
|
571
|
+
var input: [1024]u8 = undefined;
|
|
572
|
+
var output: [1024]u8 = undefined;
|
|
573
|
+
|
|
574
|
+
var stream = std.io.fixedBufferStream(&input);
|
|
575
|
+
try actions.fmt(&stream.writer(), false);
|
|
576
|
+
var len = stream.getWritten().len;
|
|
577
|
+
@memcpy(output[0..len], input[0..len]);
|
|
578
|
+
|
|
579
|
+
var find: [16]u8 = undefined;
|
|
580
|
+
var repl: [22]u8 = undefined;
|
|
581
|
+
if (p1 != 0 and actions.p1.damage != 0) {
|
|
582
|
+
const n = try std.fmt.bufPrint(&find, "P1 = (damage:{d}", .{actions.p1.damage});
|
|
583
|
+
const r = try std.fmt.bufPrint(&repl, "P1 = (damage:{d}…{d}", .{ actions.p1.damage, p1 });
|
|
584
|
+
assert(std.mem.replace(u8, input[0..len], n, r, output[0 .. len + 6]) == 1);
|
|
585
|
+
@memcpy(input[0 .. len + 6], output[0 .. len + 6]);
|
|
586
|
+
len += 6;
|
|
587
|
+
}
|
|
588
|
+
if (p2 != 0 and actions.p2.damage != 0) {
|
|
589
|
+
const n = try std.fmt.bufPrint(&find, "P2 = (damage:{d}", .{actions.p2.damage});
|
|
590
|
+
const r = try std.fmt.bufPrint(&repl, "P2 = (damage:{d}…{d}", .{ actions.p2.damage, p2 });
|
|
591
|
+
assert(std.mem.replace(u8, input[0..len], n, r, output[0 .. len + 6]) == 1);
|
|
592
|
+
len += 6;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
try writer.writeAll(output[0..len]);
|
|
426
596
|
}
|