@pkmn/engine 0.1.0-dev.14ca6108 → 0.1.0-dev.15f64810
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 +34 -23
- 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 +132 -29
- 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 -49
- package/src/lib/gen1/mechanics.zig +327 -307
- 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 +1633 -484
- 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/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
|
}
|
|
@@ -201,6 +204,8 @@ pub const Options = struct {
|
|
|
201
204
|
metronome: bool = false,
|
|
202
205
|
};
|
|
203
206
|
|
|
207
|
+
pub const MAX_FRONTIER = 14;
|
|
208
|
+
|
|
204
209
|
pub fn transitions(
|
|
205
210
|
battle: anytype,
|
|
206
211
|
c1: Choice,
|
|
@@ -211,8 +216,8 @@ pub fn transitions(
|
|
|
211
216
|
) !?Stats {
|
|
212
217
|
var stats: Stats = .{};
|
|
213
218
|
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
const actions = options.actions;
|
|
220
|
+
const cap = options.cap;
|
|
216
221
|
|
|
217
222
|
var seen = std.AutoHashMap(Actions, void).init(allocator);
|
|
218
223
|
defer seen.deinit();
|
|
@@ -232,8 +237,8 @@ pub fn transitions(
|
|
|
232
237
|
_ = try b.update(c1, c2, &opts);
|
|
233
238
|
stats.updates += 1;
|
|
234
239
|
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
const p1 = b.side(.P1);
|
|
241
|
+
const p2 = b.side(.P2);
|
|
237
242
|
|
|
238
243
|
var p: Rational(u128) = .{ .p = 0, .q = 1 };
|
|
239
244
|
try frontier.append(opts.chance.actions);
|
|
@@ -247,13 +252,13 @@ pub fn transitions(
|
|
|
247
252
|
var i: usize = 0;
|
|
248
253
|
assert(frontier.items.len == 1);
|
|
249
254
|
while (i < frontier.items.len) : (i += 1) {
|
|
250
|
-
|
|
255
|
+
const template = frontier.items[i];
|
|
251
256
|
opts.calc.overrides.durations = .{
|
|
252
257
|
.p1 = template.p1.durations,
|
|
253
258
|
.p2 = template.p2.durations,
|
|
254
259
|
};
|
|
255
260
|
|
|
256
|
-
try debug(writer, template, true, .{ .color = i, .bold = true, .background = true });
|
|
261
|
+
try debug(writer, template, true, .{ .color = i, .bold = true, .background = true, .indent = false });
|
|
257
262
|
|
|
258
263
|
var a: Actions = .{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
|
|
259
264
|
|
|
@@ -281,7 +286,7 @@ pub fn transitions(
|
|
|
281
286
|
for (Rolls.moveSlot(template.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
|
|
282
287
|
for (Rolls.moveSlot(template.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
|
|
283
288
|
for (Rolls.multiHit(template.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
|
|
284
|
-
for (Rolls.multiHit(template.p2,
|
|
289
|
+
for (Rolls.multiHit(template.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
|
|
285
290
|
for (Rolls.secondaryChance(template.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
|
|
286
291
|
for (Rolls.secondaryChance(template.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
|
|
287
292
|
for (Rolls.criticalHit(template.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
|
|
@@ -308,21 +313,21 @@ pub fn transitions(
|
|
|
308
313
|
const p2_max: u8 = @intCast(p2_dmg.min);
|
|
309
314
|
_ = cap;
|
|
310
315
|
|
|
311
|
-
//
|
|
312
|
-
// try Rolls.coalesce(.P1, @
|
|
313
|
-
//
|
|
314
|
-
// try Rolls.coalesce(.P2, @
|
|
316
|
+
// const p1_max = if (p1_min != 0) p1_min else
|
|
317
|
+
// try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), &opts.calc.summaries, cap);
|
|
318
|
+
// const p2_max =
|
|
319
|
+
// try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), &opts.calc.summaries, cap);
|
|
315
320
|
|
|
316
321
|
if (opts.chance.actions.matches(template)) {
|
|
317
322
|
if (!opts.chance.actions.eql(a)) {
|
|
318
|
-
try debug(writer, opts.chance.actions, false, .{ .color = i, .dim = true });
|
|
323
|
+
if (!summary) try debug(writer, opts.chance.actions, false, .{ .color = i, .dim = true });
|
|
319
324
|
|
|
320
325
|
p1_min = p1_max;
|
|
321
326
|
p2_dmg.min = p2_max;
|
|
322
327
|
continue;
|
|
323
328
|
}
|
|
324
329
|
|
|
325
|
-
try debug(writer, opts.chance.actions, false, .{ .color = i });
|
|
330
|
+
if (!summary) try debug(writer, opts.chance.actions, false, .{ .color = i });
|
|
326
331
|
|
|
327
332
|
for (p1_dmg.min..@as(u9, p1_max) + 1) |p1d| {
|
|
328
333
|
for (p2_dmg.min..@as(u9, p2_max) + 1) |p2d| {
|
|
@@ -346,17 +351,25 @@ pub fn transitions(
|
|
|
346
351
|
return error.TestUnexpectedResult;
|
|
347
352
|
}
|
|
348
353
|
} else {
|
|
349
|
-
|
|
354
|
+
|
|
350
355
|
|
|
351
356
|
if (!matches(opts.chance.actions, i, frontier.items)) {
|
|
352
357
|
try frontier.append(opts.chance.actions);
|
|
353
358
|
|
|
359
|
+
try debug(writer, opts.chance.actions, false, .{ .dim = true, .newline = false });
|
|
360
|
+
try writer.writeAll(" → ");
|
|
354
361
|
try debug(writer, opts.chance.actions, true, .{
|
|
355
362
|
.color = frontier.items.len - 1,
|
|
356
363
|
.dim = true,
|
|
357
|
-
.background = true
|
|
364
|
+
.background = true,
|
|
365
|
+
.indent = false,
|
|
358
366
|
});
|
|
367
|
+
} else if (!summary) {
|
|
368
|
+
try debug(writer, opts.chance.actions, false, .{ .dim = true });
|
|
359
369
|
}
|
|
370
|
+
|
|
371
|
+
// if (opts.chance.actions.p1.damage == 0) p1_max = 255;
|
|
372
|
+
// if (opts.chance.actions.p2.damage == 0) p2_max = 255;
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
p1_min = p1_max;
|
|
@@ -367,6 +380,12 @@ pub fn transitions(
|
|
|
367
380
|
p1_dmg.min = p1_min;
|
|
368
381
|
|
|
369
382
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
383
|
+
|
|
384
|
+
if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
|
|
385
|
+
p.reduce();
|
|
386
|
+
try writer.print(" = {s} ({d:.2}%)\n\n", .{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))});
|
|
387
|
+
}
|
|
388
|
+
|
|
370
389
|
}
|
|
371
390
|
|
|
372
391
|
assert(frontier.items.len == i);
|
|
@@ -387,7 +406,86 @@ pub fn transitions(
|
|
|
387
406
|
return stats;
|
|
388
407
|
}
|
|
389
408
|
|
|
390
|
-
|
|
409
|
+
pub fn update(
|
|
410
|
+
battle: anytype,
|
|
411
|
+
c1: Choice,
|
|
412
|
+
c2: Choice,
|
|
413
|
+
options: anytype,
|
|
414
|
+
allocator: std.mem.Allocator,
|
|
415
|
+
writer: anytype,
|
|
416
|
+
transition: bool,
|
|
417
|
+
) !Result {
|
|
418
|
+
options.chance.reset();
|
|
419
|
+
|
|
420
|
+
if (!pkmn.options.chance or !pkmn.options.calc or @TypeOf(battle.rng) != data.PRNG) {
|
|
421
|
+
return battle.update(c1, c2, options);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
var copy = battle.*;
|
|
425
|
+
const actions = options.chance.actions;
|
|
426
|
+
|
|
427
|
+
// Perfom the actual update
|
|
428
|
+
const result = battle.update(c1, c2, options);
|
|
429
|
+
|
|
430
|
+
// Ensure we can encode the diffs in less than MAX_DIFFS bytes.
|
|
431
|
+
var buf: [helpers.MAX_DIFFS]u8 = undefined;
|
|
432
|
+
var stream: protocol.ByteStream = .{ .buffer = &buf };
|
|
433
|
+
const n = try helpers.diff(©, battle, stream.writer());
|
|
434
|
+
|
|
435
|
+
// Applying the diff to the battle should take us back to the original copy
|
|
436
|
+
// of the battle (ignoring the RNG).
|
|
437
|
+
var patched = battle.*;
|
|
438
|
+
patched.rng = copy.rng;
|
|
439
|
+
helpers.patch(&patched, buf[0..n]);
|
|
440
|
+
try expectEqual(copy, patched);
|
|
441
|
+
|
|
442
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return result;
|
|
443
|
+
|
|
444
|
+
if (transition) {
|
|
445
|
+
// Ensure we can generate all transitions from the same original state
|
|
446
|
+
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
447
|
+
// the transitions function relies on RNG for discovery of states)
|
|
448
|
+
if (try transitions(unfix(copy), c1, c2, allocator, writer, .{
|
|
449
|
+
.actions = actions,
|
|
450
|
+
.cap = true,
|
|
451
|
+
})) |stats| try expect(stats.frontier <= MAX_FRONTIER);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Demonstrate that we can produce the same state by forcing the RNG to behave the
|
|
455
|
+
// same as we observed - note that because we do not pass in a durations override
|
|
456
|
+
// mask none of the durations will be extended.
|
|
457
|
+
var override = pkmn.battle.options(
|
|
458
|
+
protocol.NULL,
|
|
459
|
+
chance.NULL,
|
|
460
|
+
Calc{ .overrides = .{ .actions = actions } },
|
|
461
|
+
);
|
|
462
|
+
const overridden = copy.update(c1, c2, &override);
|
|
463
|
+
try expectEqual(result, overridden);
|
|
464
|
+
|
|
465
|
+
// The actual battle excluding its RNG field should match a copy updated with
|
|
466
|
+
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
467
|
+
copy.rng = battle.rng;
|
|
468
|
+
try expectEqual(copy, battle.*);
|
|
469
|
+
|
|
470
|
+
return result;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
474
|
+
return .{
|
|
475
|
+
.sides = actual.sides,
|
|
476
|
+
.turn = actual.turn,
|
|
477
|
+
.last_damage = actual.last_damage,
|
|
478
|
+
.last_moves = actual.last_moves,
|
|
479
|
+
.rng = .{ .src = .{
|
|
480
|
+
.seed = if (pkmn.options.showdown)
|
|
481
|
+
0x12345678
|
|
482
|
+
else
|
|
483
|
+
.{ 123, 234, 56, 78, 9, 101, 112, 131, 4 },
|
|
484
|
+
} },
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
391
489
|
for (frontier, 0..) |f, j| {
|
|
392
490
|
// TODO: is skipping this redundant check worth it?
|
|
393
491
|
if (i == j) continue;
|
|
@@ -408,19 +506,24 @@ const Style = struct {
|
|
|
408
506
|
bold: bool = false,
|
|
409
507
|
background: bool = false,
|
|
410
508
|
dim: bool = false,
|
|
509
|
+
newline: bool = true,
|
|
510
|
+
indent: bool = true,
|
|
411
511
|
};
|
|
412
512
|
|
|
413
513
|
fn debug(writer: anytype, actions: Actions, shape: bool, style: Style) !void {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
514
|
+
if (style.indent) try writer.writeAll(" ");
|
|
515
|
+
if (tty) {
|
|
516
|
+
const mod: usize = if (style.dim) 2 else 1;
|
|
517
|
+
const background: usize = if (style.background) 4 else 3;
|
|
518
|
+
const color: usize = if (style.color) |c| (c % 6) + 1 else 7;
|
|
519
|
+
|
|
520
|
+
if (style.dim or style.bold) try writer.print("\x1b[{d}m", .{mod});
|
|
521
|
+
try writer.print("\x1b[{d}{d}m", .{ background, color });
|
|
522
|
+
try actions.fmt(writer, shape);
|
|
523
|
+
try writer.writeAll("\x1b[0m");
|
|
524
|
+
} else {
|
|
525
|
+
if (style.dim) try writer.writeAll(" ");
|
|
526
|
+
try actions.fmt(writer, shape);
|
|
527
|
+
}
|
|
528
|
+
if (style.newline) try writer.writeByte('\n');
|
|
426
529
|
}
|