@pkmn/engine 0.1.0-dev.6158d5ad → 0.1.0-dev.62399ed5
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/ids.json +12 -12
- package/build/pkg/data/layout.json +2 -2
- package/build/pkg/data/protocol.json +69 -12
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js +2 -2
- 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 +61 -42
- 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 +102 -104
- package/package.json +43 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +12 -24
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +9 -2
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +802 -327
- package/src/lib/common/rational.zig +113 -19
- package/src/lib/common/rng.zig +26 -17
- package/src/lib/gen1/calc.zig +219 -50
- package/src/lib/gen1/chance.zig +140 -125
- package/src/lib/gen1/data/moves.zig +192 -180
- package/src/lib/gen1/data/species.zig +3 -3
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +50 -62
- package/src/lib/gen1/helpers.zig +148 -82
- package/src/lib/gen1/mechanics.zig +403 -417
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1315 -0
- package/src/lib/gen2/data/items.zig +12 -18
- package/src/lib/gen2/data/moves.zig +663 -569
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +106 -79
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +45 -19
- package/src/lib/gen2/mechanics.zig +3587 -9
- package/src/lib/{binding/node.zig → node.zig} +10 -13
- 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 -892
- package/src/lib/gen2/README.md +0 -308
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -29,7 +29,6 @@ const Optional = optional.Optional;
|
|
|
29
29
|
const Rational = rational.Rational;
|
|
30
30
|
|
|
31
31
|
const PointerType = util.PointerType;
|
|
32
|
-
const FieldType = util.FieldType;
|
|
33
32
|
|
|
34
33
|
const Actions = chance.Actions;
|
|
35
34
|
const Action = chance.Action;
|
|
@@ -39,6 +38,9 @@ const Duration = chance.Duration;
|
|
|
39
38
|
|
|
40
39
|
const Rolls = helpers.Rolls;
|
|
41
40
|
|
|
41
|
+
const tty = true; // DEBUG
|
|
42
|
+
const summary = false; // DEBUG
|
|
43
|
+
|
|
42
44
|
/// Information relevant to damage calculation that occured during a Generation I battle `update`.
|
|
43
45
|
pub const Summaries = extern struct {
|
|
44
46
|
/// Relevant information for Player 1.
|
|
@@ -51,7 +53,7 @@ pub const Summaries = extern struct {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/// Returns the `Summary` for the given `player`.
|
|
54
|
-
pub
|
|
56
|
+
pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
|
|
55
57
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Summaries);
|
|
56
58
|
return if (player == .P1) &self.p1 else &self.p2;
|
|
57
59
|
}
|
|
@@ -70,8 +72,10 @@ pub const Summary = extern struct {
|
|
|
70
72
|
/// The final computed damage that gets applied to the Pokémon. May exceed the target's HP
|
|
71
73
|
// (to determine the *actual* damage done compare the target's stored HP before and after).
|
|
72
74
|
final: u16 = 0,
|
|
73
|
-
/// Whether higher damage will saturate / result in the same outcome (e.g. additional
|
|
74
|
-
/// 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.
|
|
75
79
|
capped: bool = false,
|
|
76
80
|
|
|
77
81
|
// NOTE: 15 bits padding
|
|
@@ -158,7 +162,7 @@ pub const Calc = struct {
|
|
|
158
162
|
|
|
159
163
|
/// Null object pattern implementation of Generation I `Calc` which does nothing, though damage
|
|
160
164
|
/// calculator support should additionally be turned off entirely via `options.calc`.
|
|
161
|
-
pub const NULL =
|
|
165
|
+
pub const NULL: Null = .{};
|
|
162
166
|
|
|
163
167
|
const Null = struct {
|
|
164
168
|
pub fn overridden(
|
|
@@ -202,6 +206,8 @@ pub const Options = struct {
|
|
|
202
206
|
metronome: bool = false,
|
|
203
207
|
};
|
|
204
208
|
|
|
209
|
+
pub const MAX_FRONTIER = 19;
|
|
210
|
+
|
|
205
211
|
pub fn transitions(
|
|
206
212
|
battle: anytype,
|
|
207
213
|
c1: Choice,
|
|
@@ -212,8 +218,8 @@ pub fn transitions(
|
|
|
212
218
|
) !?Stats {
|
|
213
219
|
var stats: Stats = .{};
|
|
214
220
|
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
const actions = options.actions;
|
|
222
|
+
const cap = options.cap;
|
|
217
223
|
|
|
218
224
|
var seen = std.AutoHashMap(Actions, void).init(allocator);
|
|
219
225
|
defer seen.deinit();
|
|
@@ -233,8 +239,8 @@ pub fn transitions(
|
|
|
233
239
|
_ = try b.update(c1, c2, &opts);
|
|
234
240
|
stats.updates += 1;
|
|
235
241
|
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
const p1 = b.side(.P1);
|
|
243
|
+
const p2 = b.side(.P2);
|
|
238
244
|
|
|
239
245
|
var p: Rational(u128) = .{ .p = 0, .q = 1 };
|
|
240
246
|
try frontier.append(opts.chance.actions);
|
|
@@ -248,15 +254,21 @@ pub fn transitions(
|
|
|
248
254
|
var i: usize = 0;
|
|
249
255
|
assert(frontier.items.len == 1);
|
|
250
256
|
while (i < frontier.items.len) : (i += 1) {
|
|
251
|
-
|
|
257
|
+
const template = frontier.items[i];
|
|
252
258
|
opts.calc.overrides.durations = .{
|
|
253
259
|
.p1 = template.p1.durations,
|
|
254
260
|
.p2 = template.p2.durations,
|
|
255
261
|
};
|
|
256
262
|
|
|
257
|
-
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
|
+
});
|
|
258
270
|
|
|
259
|
-
var a =
|
|
271
|
+
var a: Actions = .{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
|
|
260
272
|
|
|
261
273
|
for (Rolls.speedTie(template.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
262
274
|
for (Rolls.sleep(template.p1, actions.p1)) |p1_slp| { a.p1.durations.sleep = p1_slp;
|
|
@@ -282,7 +294,7 @@ pub fn transitions(
|
|
|
282
294
|
for (Rolls.moveSlot(template.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
283
295
|
for (Rolls.moveSlot(template.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
284
296
|
for (Rolls.multiHit(template.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
285
|
-
for (Rolls.multiHit(template.p2,
|
|
297
|
+
for (Rolls.multiHit(template.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
286
298
|
for (Rolls.secondaryChance(template.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
287
299
|
for (Rolls.secondaryChance(template.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
288
300
|
for (Rolls.criticalHit(template.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
@@ -291,13 +303,17 @@ pub fn transitions(
|
|
|
291
303
|
var p1_dmg = Rolls.damage(template.p1, p1_hit);
|
|
292
304
|
while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
|
|
293
305
|
a.p1.damage = @intCast(p1_dmg.min);
|
|
294
|
-
var p1_min: u8 = 0;
|
|
295
306
|
|
|
296
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
|
+
|
|
297
312
|
while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
|
|
298
313
|
a.p2.damage = @intCast(p2_dmg.min);
|
|
299
314
|
|
|
300
315
|
opts.calc.overrides.actions = a;
|
|
316
|
+
opts.calc.summaries = .{};
|
|
301
317
|
opts.chance = .{ .probability = .{}, .actions = actions };
|
|
302
318
|
const q = &opts.chance.probability;
|
|
303
319
|
|
|
@@ -305,28 +321,40 @@ pub fn transitions(
|
|
|
305
321
|
_ = try b.update(c1, c2, &opts);
|
|
306
322
|
stats.updates += 1;
|
|
307
323
|
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// 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);
|
|
316
331
|
|
|
317
332
|
if (opts.chance.actions.matches(template)) {
|
|
318
333
|
if (!opts.chance.actions.eql(a)) {
|
|
319
|
-
|
|
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
|
+
}
|
|
320
342
|
|
|
321
|
-
|
|
343
|
+
p1_dmg.min = p1_max;
|
|
322
344
|
p2_dmg.min = p2_max;
|
|
323
345
|
continue;
|
|
324
346
|
}
|
|
325
347
|
|
|
326
|
-
|
|
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
|
+
}
|
|
327
355
|
|
|
328
|
-
for (
|
|
329
|
-
for (p2_dmg.min
|
|
356
|
+
for (p1_min..p1_max + 1) |p1d| {
|
|
357
|
+
for (p2_dmg.min..p2_max + 1) |p2d| {
|
|
330
358
|
var acts = opts.chance.actions;
|
|
331
359
|
acts.p1.damage = @intCast(p1d);
|
|
332
360
|
acts.p2.damage = @intCast(p2d);
|
|
@@ -337,7 +365,7 @@ pub fn transitions(
|
|
|
337
365
|
}
|
|
338
366
|
}
|
|
339
367
|
|
|
340
|
-
if (p1_max !=
|
|
368
|
+
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
|
|
341
369
|
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
|
|
342
370
|
try p.add(q);
|
|
343
371
|
stats.saved += 1;
|
|
@@ -347,27 +375,46 @@ pub fn transitions(
|
|
|
347
375
|
return error.TestUnexpectedResult;
|
|
348
376
|
}
|
|
349
377
|
} else {
|
|
350
|
-
try debug(writer, opts.chance.actions, false, .{ .dim = true });
|
|
351
|
-
|
|
352
378
|
if (!matches(opts.chance.actions, i, frontier.items)) {
|
|
353
379
|
try frontier.append(opts.chance.actions);
|
|
354
380
|
|
|
355
|
-
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,
|
|
356
390
|
.color = frontier.items.len - 1,
|
|
357
391
|
.dim = true,
|
|
358
|
-
.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,
|
|
359
400
|
});
|
|
360
401
|
}
|
|
361
402
|
}
|
|
362
403
|
|
|
363
|
-
|
|
404
|
+
p1_dmg.min = p1_max;
|
|
364
405
|
p2_dmg.min = p2_max;
|
|
365
406
|
}
|
|
366
407
|
|
|
367
|
-
assert(p1_min > 0 or p1_dmg.min == 0);
|
|
368
|
-
p1_dmg.min = p1_min;
|
|
369
|
-
|
|
370
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
|
+
|
|
371
418
|
}
|
|
372
419
|
|
|
373
420
|
assert(frontier.items.len == i);
|
|
@@ -388,7 +435,86 @@ pub fn transitions(
|
|
|
388
435
|
return stats;
|
|
389
436
|
}
|
|
390
437
|
|
|
391
|
-
|
|
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 {
|
|
392
518
|
for (frontier, 0..) |f, j| {
|
|
393
519
|
// TODO: is skipping this redundant check worth it?
|
|
394
520
|
if (i == j) continue;
|
|
@@ -405,23 +531,66 @@ fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
|
|
|
405
531
|
}
|
|
406
532
|
|
|
407
533
|
const Style = struct {
|
|
534
|
+
shape: bool = false,
|
|
535
|
+
p1_max: u9 = 0,
|
|
536
|
+
p2_max: u9 = 0,
|
|
408
537
|
color: ?usize = null,
|
|
409
538
|
bold: bool = false,
|
|
410
539
|
background: bool = false,
|
|
411
540
|
dim: bool = false,
|
|
541
|
+
newline: bool = true,
|
|
542
|
+
indent: bool = true,
|
|
412
543
|
};
|
|
413
544
|
|
|
414
|
-
fn debug(writer: anytype, actions: Actions,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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]);
|
|
427
596
|
}
|