@pkmn/engine 0.1.0-dev.9f75c7ae → 0.1.0-dev.a0167a9d
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 +82 -90
- package/package.json +31 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +4 -4
- 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 +393 -396
- package/src/lib/common/rational.zig +109 -15
- package/src/lib/common/rng.zig +11 -11
- package/src/lib/gen1/calc.zig +269 -97
- package/src/lib/gen1/chance.zig +126 -160
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +21 -21
- package/src/lib/gen1/helpers.zig +191 -206
- package/src/lib/gen1/mechanics.zig +308 -285
- package/src/lib/gen2/chance.zig +211 -121
- package/src/lib/gen2/data.zig +13 -11
- package/src/lib/gen2/generated.zig +15 -24
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +855 -413
- 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
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.
|
|
@@ -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
|
|
@@ -131,9 +136,7 @@ pub const Calc = struct {
|
|
|
131
136
|
const set = @field(if (player == .P1) durations.p1 else durations.p2, @tagName(field));
|
|
132
137
|
if (set == 0) return null;
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
const overrides = if (player == .P1) actions.p1.durations else actions.p2.durations;
|
|
136
|
-
return @field(overrides, @tagName(field)) != 0;
|
|
139
|
+
return null; // TODO
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
pub fn base(self: *Calc, player: Player, val: u16) void {
|
|
@@ -196,11 +199,19 @@ pub const Stats = struct {
|
|
|
196
199
|
|
|
197
200
|
pub const Options = struct {
|
|
198
201
|
actions: Actions = .{},
|
|
202
|
+
durations: Durations = .{},
|
|
199
203
|
seed: ?u64 = null,
|
|
200
204
|
cap: bool = false,
|
|
201
205
|
metronome: bool = false,
|
|
202
206
|
};
|
|
203
207
|
|
|
208
|
+
pub const MAX_FRONTIER = 19;
|
|
209
|
+
|
|
210
|
+
const Element = struct {
|
|
211
|
+
actions: Actions,
|
|
212
|
+
durations: Durations,
|
|
213
|
+
};
|
|
214
|
+
|
|
204
215
|
pub fn transitions(
|
|
205
216
|
battle: anytype,
|
|
206
217
|
c1: Choice,
|
|
@@ -211,92 +222,97 @@ pub fn transitions(
|
|
|
211
222
|
) !?Stats {
|
|
212
223
|
var stats: Stats = .{};
|
|
213
224
|
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
const actions = options.actions;
|
|
226
|
+
const durations = options.durations;
|
|
227
|
+
const cap = options.cap;
|
|
216
228
|
|
|
217
229
|
var seen = std.AutoHashMap(Actions, void).init(allocator);
|
|
218
230
|
defer seen.deinit();
|
|
219
|
-
var frontier = std.ArrayList(
|
|
231
|
+
var frontier = std.ArrayList(Element).init(allocator);
|
|
220
232
|
defer frontier.deinit();
|
|
221
233
|
|
|
222
234
|
var opts = pkmn.battle.options(
|
|
223
235
|
protocol.NULL,
|
|
224
|
-
Chance(Rational(u128)){ .probability = .{}, .actions = actions },
|
|
225
|
-
Calc{ .overrides = .{ .actions = actions, .durations =
|
|
226
|
-
.p1 = actions.p1.durations,
|
|
227
|
-
.p2 = actions.p2.durations,
|
|
228
|
-
} } },
|
|
236
|
+
Chance(Rational(u128)){ .probability = .{}, .actions = actions, .durations = durations },
|
|
237
|
+
Calc{ .overrides = .{ .actions = actions, .durations = durations } },
|
|
229
238
|
);
|
|
230
239
|
|
|
231
240
|
var b = battle;
|
|
232
241
|
_ = try b.update(c1, c2, &opts);
|
|
233
242
|
stats.updates += 1;
|
|
234
243
|
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
const p1 = b.side(.P1);
|
|
245
|
+
const p2 = b.side(.P2);
|
|
237
246
|
|
|
238
247
|
var p: Rational(u128) = .{ .p = 0, .q = 1 };
|
|
239
|
-
try frontier.append(opts.chance.actions);
|
|
248
|
+
try frontier.append(.{ .actions = opts.chance.actions, .durations = opts.chance.durations });
|
|
240
249
|
|
|
241
250
|
// zig fmt: off
|
|
242
|
-
for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
|
|
243
|
-
for (Rolls.metronome(frontier.items[0].p2)) |p2_move| {
|
|
251
|
+
for (Rolls.metronome(frontier.items[0].actions.p1)) |p1_move| {
|
|
252
|
+
for (Rolls.metronome(frontier.items[0].actions.p2)) |p2_move| {
|
|
244
253
|
|
|
245
254
|
if (!options.metronome and (p1_move != .None or p2_move != .None)) return null;
|
|
246
255
|
|
|
247
256
|
var i: usize = 0;
|
|
248
257
|
assert(frontier.items.len == 1);
|
|
249
258
|
while (i < frontier.items.len) : (i += 1) {
|
|
250
|
-
|
|
251
|
-
opts.calc.overrides.durations = .{
|
|
252
|
-
.p1 = template.p1.durations,
|
|
253
|
-
.p2 = template.p2.durations,
|
|
254
|
-
};
|
|
259
|
+
const template = frontier.items[i];
|
|
255
260
|
|
|
256
|
-
try debug(writer, template,
|
|
261
|
+
try debug(writer, template.actions, .{
|
|
262
|
+
.shape = true,
|
|
263
|
+
.color = i,
|
|
264
|
+
.bold = true,
|
|
265
|
+
.background = true,
|
|
266
|
+
.indent = false,
|
|
267
|
+
});
|
|
257
268
|
|
|
258
269
|
var a: Actions = .{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
for (Rolls.
|
|
262
|
-
for (Rolls.sleep(template.
|
|
263
|
-
for (Rolls.
|
|
264
|
-
for (Rolls.disable(template.
|
|
265
|
-
for (Rolls.
|
|
266
|
-
for (Rolls.confusion(template.
|
|
267
|
-
for (Rolls.
|
|
268
|
-
for (Rolls.confused(template.
|
|
269
|
-
for (Rolls.
|
|
270
|
-
for (Rolls.paralyzed(template.
|
|
271
|
-
for (Rolls.
|
|
272
|
-
for (Rolls.attacking(template.
|
|
273
|
-
for (Rolls.
|
|
274
|
-
for (Rolls.binding(template.
|
|
275
|
-
for (Rolls.
|
|
276
|
-
for (Rolls.duration(template.
|
|
277
|
-
for (Rolls.
|
|
278
|
-
for (Rolls.hit(template.
|
|
279
|
-
for (Rolls.
|
|
280
|
-
for (Rolls.psywave(template.
|
|
281
|
-
for (Rolls.
|
|
282
|
-
for (Rolls.moveSlot(template.
|
|
283
|
-
for (Rolls.
|
|
284
|
-
for (Rolls.multiHit(template.
|
|
285
|
-
for (Rolls.
|
|
286
|
-
for (Rolls.secondaryChance(template.
|
|
287
|
-
for (Rolls.
|
|
288
|
-
for (Rolls.criticalHit(template.
|
|
289
|
-
|
|
290
|
-
|
|
270
|
+
var d: Durations = .{};
|
|
271
|
+
|
|
272
|
+
for (Rolls.speedTie(template.actions.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
273
|
+
for (Rolls.sleep(template.durations.p1, durations.p1)) |p1_slp| { d.p1.sleep = p1_slp;
|
|
274
|
+
for (Rolls.sleep(template.durations.p2, durations.p2)) |p2_slp| { d.p2.sleep = p2_slp;
|
|
275
|
+
for (Rolls.disable(template.durations.p1, durations.p1, p1_slp)) |p1_disable| { d.p1.disable = p1_disable;
|
|
276
|
+
for (Rolls.disable(template.durations.p2, durations.p2, p2_slp)) |p2_disable| { d.p2.disable = p2_disable;
|
|
277
|
+
for (Rolls.confusion(template.durations.p1, durations.p1, p1_slp)) |p1_cfz| { d.p1.confusion = p1_cfz;
|
|
278
|
+
for (Rolls.confusion(template.durations.p2, durations.p2, p1_slp)) |p2_cfz| { d.p2.confusion = p2_cfz;
|
|
279
|
+
for (Rolls.confused(template.actions.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
280
|
+
for (Rolls.confused(template.actions.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
281
|
+
for (Rolls.paralyzed(template.actions.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
282
|
+
for (Rolls.paralyzed(template.actions.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
283
|
+
for (Rolls.attacking(template.durations.p1, durations.p1, p1_par)) |p1_atk| { d.p1.attacking = p1_atk;
|
|
284
|
+
for (Rolls.attacking(template.durations.p2, durations.p2, p2_par)) |p2_atk| { d.p2.attacking = p2_atk;
|
|
285
|
+
for (Rolls.binding(template.durations.p1, durations.p1, p1_par)) |p1_bind| { d.p1.binding = p1_bind;
|
|
286
|
+
for (Rolls.binding(template.durations.p2, durations.p2, p2_par)) |p2_bind| { d.p2.binding = p2_bind;
|
|
287
|
+
for (Rolls.duration(template.actions.p1, p1_par)) |p1_dur| { a.p1.duration = p1_dur;
|
|
288
|
+
for (Rolls.duration(template.actions.p2, p2_par)) |p2_dur| { a.p2.duration = p2_dur;
|
|
289
|
+
for (Rolls.hit(template.actions.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
290
|
+
for (Rolls.hit(template.actions.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
291
|
+
for (Rolls.psywave(template.actions.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
292
|
+
for (Rolls.psywave(template.actions.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
|
|
293
|
+
for (Rolls.moveSlot(template.actions.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
294
|
+
for (Rolls.moveSlot(template.actions.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
295
|
+
for (Rolls.multiHit(template.actions.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
296
|
+
for (Rolls.multiHit(template.actions.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
297
|
+
for (Rolls.secondaryChance(template.actions.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
298
|
+
for (Rolls.secondaryChance(template.actions.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
299
|
+
for (Rolls.criticalHit(template.actions.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
300
|
+
for (Rolls.criticalHit(template.actions.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
|
|
301
|
+
|
|
302
|
+
var p1_dmg = Rolls.damage(template.actions.p1, p1_hit);
|
|
291
303
|
while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
|
|
292
304
|
a.p1.damage = @intCast(p1_dmg.min);
|
|
293
|
-
var p1_min: u8 = 0;
|
|
294
305
|
|
|
295
|
-
var p2_dmg = Rolls.damage(template.p2, p2_hit);
|
|
306
|
+
var p2_dmg = Rolls.damage(template.actions.p2, p2_hit);
|
|
307
|
+
|
|
308
|
+
const p1_min: u9 = p1_dmg.min;
|
|
309
|
+
const p2_min: u9 = p2_dmg.min;
|
|
310
|
+
|
|
296
311
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
297
312
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
298
313
|
|
|
299
314
|
opts.calc.overrides.actions = a;
|
|
315
|
+
opts.calc.summaries = .{};
|
|
300
316
|
opts.chance = .{ .probability = .{}, .actions = actions };
|
|
301
317
|
const q = &opts.chance.probability;
|
|
302
318
|
|
|
@@ -304,28 +320,40 @@ pub fn transitions(
|
|
|
304
320
|
_ = try b.update(c1, c2, &opts);
|
|
305
321
|
stats.updates += 1;
|
|
306
322
|
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// try Rolls.coalesce(.P2, @intCast(u8, p2_dmg.min), &opts.calc.summaries, cap);
|
|
323
|
+
const summaries = &opts.calc.summaries;
|
|
324
|
+
const p1_max: u9 = if (p2_dmg.min != p2_min)
|
|
325
|
+
p1_dmg.min
|
|
326
|
+
else
|
|
327
|
+
try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
|
|
328
|
+
const p2_max: u9 =
|
|
329
|
+
try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
315
330
|
|
|
316
|
-
if (opts.chance.actions.matches(template)) {
|
|
331
|
+
if (opts.chance.actions.matches(template.actions)) {
|
|
317
332
|
if (!opts.chance.actions.eql(a)) {
|
|
318
|
-
|
|
333
|
+
if (!summary) {
|
|
334
|
+
try debug(writer, opts.chance.actions, .{
|
|
335
|
+
.p1_max = p1_max,
|
|
336
|
+
.p2_max = p2_max,
|
|
337
|
+
.color = i,
|
|
338
|
+
.dim = true,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
319
341
|
|
|
320
|
-
|
|
342
|
+
p1_dmg.min = p1_max;
|
|
321
343
|
p2_dmg.min = p2_max;
|
|
322
344
|
continue;
|
|
323
345
|
}
|
|
324
346
|
|
|
325
|
-
|
|
347
|
+
if (!summary) {
|
|
348
|
+
try debug(writer, opts.chance.actions, .{
|
|
349
|
+
.p1_max = p1_max,
|
|
350
|
+
.p2_max = p2_max,
|
|
351
|
+
.color = i,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
326
354
|
|
|
327
|
-
for (
|
|
328
|
-
for (p2_dmg.min
|
|
355
|
+
for (p1_min..p1_max + 1) |p1d| {
|
|
356
|
+
for (p2_dmg.min..p2_max + 1) |p2d| {
|
|
329
357
|
var acts = opts.chance.actions;
|
|
330
358
|
acts.p1.damage = @intCast(p1d);
|
|
331
359
|
acts.p2.damage = @intCast(p2d);
|
|
@@ -336,7 +364,7 @@ pub fn transitions(
|
|
|
336
364
|
}
|
|
337
365
|
}
|
|
338
366
|
|
|
339
|
-
if (p1_max !=
|
|
367
|
+
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
340
368
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
341
369
|
try p.add(q);
|
|
342
370
|
stats.saved += 1;
|
|
@@ -346,27 +374,49 @@ pub fn transitions(
|
|
|
346
374
|
return error.TestUnexpectedResult;
|
|
347
375
|
}
|
|
348
376
|
} else {
|
|
349
|
-
try debug(writer, opts.chance.actions, false, .{ .dim = true });
|
|
350
|
-
|
|
351
377
|
if (!matches(opts.chance.actions, i, frontier.items)) {
|
|
352
|
-
try frontier.append(
|
|
378
|
+
try frontier.append(.{
|
|
379
|
+
.actions = opts.chance.actions,
|
|
380
|
+
.durations = opts.chance.durations,
|
|
381
|
+
});
|
|
353
382
|
|
|
354
|
-
try debug(writer, opts.chance.actions,
|
|
383
|
+
try debug(writer, opts.chance.actions, .{
|
|
384
|
+
.p1_max = p1_max,
|
|
385
|
+
.p2_max = p2_max,
|
|
386
|
+
.dim = true,
|
|
387
|
+
.newline = false,
|
|
388
|
+
});
|
|
389
|
+
try writer.writeAll(" → ");
|
|
390
|
+
try debug(writer, opts.chance.actions, .{
|
|
391
|
+
.shape = true,
|
|
355
392
|
.color = frontier.items.len - 1,
|
|
356
393
|
.dim = true,
|
|
357
|
-
.background = true
|
|
394
|
+
.background = true,
|
|
395
|
+
.indent = false,
|
|
396
|
+
});
|
|
397
|
+
} else if (!summary) {
|
|
398
|
+
try debug(writer, opts.chance.actions, .{
|
|
399
|
+
.p1_max = p1_max,
|
|
400
|
+
.p2_max = p2_max,
|
|
401
|
+
.dim = true,
|
|
358
402
|
});
|
|
359
403
|
}
|
|
360
404
|
}
|
|
361
405
|
|
|
362
|
-
|
|
406
|
+
p1_dmg.min = p1_max;
|
|
363
407
|
p2_dmg.min = p2_max;
|
|
364
408
|
}
|
|
365
409
|
|
|
366
|
-
assert(p1_min > 0 or p1_dmg.min == 0);
|
|
367
|
-
p1_dmg.min = p1_min;
|
|
368
|
-
|
|
369
410
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
411
|
+
|
|
412
|
+
if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
|
|
413
|
+
p.reduce();
|
|
414
|
+
try writer.print(
|
|
415
|
+
" = {s} ({d:.2}%)\n\n",
|
|
416
|
+
.{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
370
420
|
}
|
|
371
421
|
|
|
372
422
|
assert(frontier.items.len == i);
|
|
@@ -387,11 +437,90 @@ pub fn transitions(
|
|
|
387
437
|
return stats;
|
|
388
438
|
}
|
|
389
439
|
|
|
390
|
-
fn
|
|
440
|
+
pub fn update(
|
|
441
|
+
battle: anytype,
|
|
442
|
+
c1: Choice,
|
|
443
|
+
c2: Choice,
|
|
444
|
+
options: anytype,
|
|
445
|
+
allocator: std.mem.Allocator,
|
|
446
|
+
writer: anytype,
|
|
447
|
+
transition: bool,
|
|
448
|
+
) !Result {
|
|
449
|
+
options.chance.reset();
|
|
450
|
+
|
|
451
|
+
if (!pkmn.options.chance or !pkmn.options.calc or @TypeOf(battle.rng) != data.PRNG) {
|
|
452
|
+
return battle.update(c1, c2, options);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
var copy = battle.*;
|
|
456
|
+
const actions = options.chance.actions;
|
|
457
|
+
|
|
458
|
+
// Perfom the actual update
|
|
459
|
+
const result = battle.update(c1, c2, options);
|
|
460
|
+
|
|
461
|
+
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
462
|
+
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
463
|
+
var stream: protocol.ByteStream = .{ .buffer = &buf };
|
|
464
|
+
const n = try helpers.diff(©, battle, stream.writer());
|
|
465
|
+
|
|
466
|
+
// Applying the diff to the battle should take us back to the original copy
|
|
467
|
+
// of the battle (ignoring the RNG).
|
|
468
|
+
var patched = battle.*;
|
|
469
|
+
patched.rng = copy.rng;
|
|
470
|
+
helpers.patch(&patched, buf[0..n]);
|
|
471
|
+
try expectEqual(copy, patched);
|
|
472
|
+
|
|
473
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return result;
|
|
474
|
+
|
|
475
|
+
if (transition) {
|
|
476
|
+
// Ensure we can generate all transitions from the same original state
|
|
477
|
+
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
478
|
+
// the transitions function relies on RNG for discovery of states)
|
|
479
|
+
if (try transitions(unfix(copy), c1, c2, allocator, writer, .{
|
|
480
|
+
.actions = actions,
|
|
481
|
+
.cap = true,
|
|
482
|
+
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Demonstrate that we can produce the same state by forcing the RNG to behave the
|
|
486
|
+
// same as we observed - note that because we do not pass in a durations override
|
|
487
|
+
// mask none of the durations will be extended.
|
|
488
|
+
var override = pkmn.battle.options(
|
|
489
|
+
protocol.NULL,
|
|
490
|
+
chance.NULL,
|
|
491
|
+
Calc{ .overrides = .{ .actions = actions } },
|
|
492
|
+
);
|
|
493
|
+
const overridden = copy.update(c1, c2, &override);
|
|
494
|
+
try expectEqual(result, overridden);
|
|
495
|
+
|
|
496
|
+
// The actual battle excluding its RNG field should match a copy updated with
|
|
497
|
+
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
498
|
+
copy.rng = battle.rng;
|
|
499
|
+
try expectEqual(copy, battle.*);
|
|
500
|
+
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
505
|
+
return .{
|
|
506
|
+
.sides = actual.sides,
|
|
507
|
+
.turn = actual.turn,
|
|
508
|
+
.last_damage = actual.last_damage,
|
|
509
|
+
.last_moves = actual.last_moves,
|
|
510
|
+
.rng = .{ .src = .{
|
|
511
|
+
.seed = if (pkmn.options.showdown)
|
|
512
|
+
0x12345678
|
|
513
|
+
else
|
|
514
|
+
.{ 123, 234, 56, 78, 9, 101, 112, 131, 4 },
|
|
515
|
+
} },
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
fn matches(actions: Actions, i: usize, frontier: []Element) bool {
|
|
391
520
|
for (frontier, 0..) |f, j| {
|
|
392
521
|
// TODO: is skipping this redundant check worth it?
|
|
393
522
|
if (i == j) continue;
|
|
394
|
-
if (f.matches(actions)) return true;
|
|
523
|
+
if (f.actions.matches(actions)) return true;
|
|
395
524
|
}
|
|
396
525
|
return false;
|
|
397
526
|
}
|
|
@@ -404,23 +533,66 @@ fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
|
404
533
|
}
|
|
405
534
|
|
|
406
535
|
const Style = struct {
|
|
536
|
+
shape: bool = false,
|
|
537
|
+
p1_max: u9 = 0,
|
|
538
|
+
p2_max: u9 = 0,
|
|
407
539
|
color: ?usize = null,
|
|
408
540
|
bold: bool = false,
|
|
409
541
|
background: bool = false,
|
|
410
542
|
dim: bool = false,
|
|
543
|
+
newline: bool = true,
|
|
544
|
+
indent: bool = true,
|
|
411
545
|
};
|
|
412
546
|
|
|
413
|
-
fn debug(writer: anytype, actions: Actions,
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
547
|
+
fn debug(writer: anytype, actions: Actions, style: Style) !void {
|
|
548
|
+
if (style.indent) try writer.writeAll(" ");
|
|
549
|
+
if (tty) {
|
|
550
|
+
const mod: usize = if (style.dim) 2 else 1;
|
|
551
|
+
const background: usize = if (style.background) 4 else 3;
|
|
552
|
+
const color: usize = if (style.color) |c| (c % 6) + 1 else 7;
|
|
553
|
+
|
|
554
|
+
if (style.dim or style.bold) try writer.print("\x1b[{d}m", .{mod});
|
|
555
|
+
try writer.print("\x1b[{d}{d}m", .{ background, color });
|
|
556
|
+
const p1 = if (style.p1_max != 0 and style.p1_max != actions.p1.damage) style.p1_max else 0;
|
|
557
|
+
const p2 = if (style.p2_max != 0 and style.p2_max != actions.p2.damage) style.p2_max else 0;
|
|
558
|
+
if (p1 != 0 or p2 != 0) {
|
|
559
|
+
assert(!style.shape);
|
|
560
|
+
try format(writer, actions, p1, p2);
|
|
561
|
+
} else {
|
|
562
|
+
try actions.fmt(writer, style.shape);
|
|
563
|
+
}
|
|
564
|
+
try writer.writeAll("\x1b[0m");
|
|
565
|
+
} else {
|
|
566
|
+
if (style.dim) try writer.writeAll(" ");
|
|
567
|
+
try actions.fmt(writer, style.shape);
|
|
568
|
+
}
|
|
569
|
+
if (style.newline) try writer.writeByte('\n');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
|
|
573
|
+
var input: [1024]u8 = undefined;
|
|
574
|
+
var output: [1024]u8 = undefined;
|
|
575
|
+
|
|
576
|
+
var stream = std.io.fixedBufferStream(&input);
|
|
577
|
+
try actions.fmt(&stream.writer(), false);
|
|
578
|
+
var len = stream.getWritten().len;
|
|
579
|
+
@memcpy(output[0..len], input[0..len]);
|
|
580
|
+
|
|
581
|
+
var find: [16]u8 = undefined;
|
|
582
|
+
var repl: [22]u8 = undefined;
|
|
583
|
+
if (p1 != 0 and actions.p1.damage != 0) {
|
|
584
|
+
const n = try std.fmt.bufPrint(&find, "P1 = (damage:{d}", .{actions.p1.damage});
|
|
585
|
+
const r = try std.fmt.bufPrint(&repl, "P1 = (damage:{d}…{d}", .{ actions.p1.damage, p1 });
|
|
586
|
+
assert(std.mem.replace(u8, input[0..len], n, r, output[0 .. len + 6]) == 1);
|
|
587
|
+
@memcpy(input[0 .. len + 6], output[0 .. len + 6]);
|
|
588
|
+
len += 6;
|
|
589
|
+
}
|
|
590
|
+
if (p2 != 0 and actions.p2.damage != 0) {
|
|
591
|
+
const n = try std.fmt.bufPrint(&find, "P2 = (damage:{d}", .{actions.p2.damage});
|
|
592
|
+
const r = try std.fmt.bufPrint(&repl, "P2 = (damage:{d}…{d}", .{ actions.p2.damage, p2 });
|
|
593
|
+
assert(std.mem.replace(u8, input[0..len], n, r, output[0 .. len + 6]) == 1);
|
|
594
|
+
len += 6;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
try writer.writeAll(output[0..len]);
|
|
426
598
|
}
|