termlings 0.1.1 → 0.1.2
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/bin/termlings.js +646 -0
- package/package.json +7 -4
- package/src/cli.ts +196 -0
package/bin/termlings.js
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/termlings/src/index.ts
|
|
4
|
+
var F = ["_", "f", "f", "f", "f", "f", "f", "f", "_"];
|
|
5
|
+
var EYES = [
|
|
6
|
+
["_", "f", "e", "f", "f", "f", "e", "f", "_"],
|
|
7
|
+
["_", "e", "f", "f", "f", "f", "f", "e", "_"],
|
|
8
|
+
["_", "f", "f", "e", "f", "e", "f", "f", "_"],
|
|
9
|
+
["_", "f", "e", "f", "f", "f", "e", "f", "_"],
|
|
10
|
+
["_", "e", "e", "f", "f", "f", "e", "e", "_"],
|
|
11
|
+
["_", "f", "e", "e", "f", "e", "e", "f", "_"],
|
|
12
|
+
["_", "f", "s", "f", "f", "f", "s", "f", "_"],
|
|
13
|
+
["_", "s", "f", "f", "f", "f", "f", "s", "_"],
|
|
14
|
+
["_", "f", "n", "f", "f", "f", "n", "f", "_"],
|
|
15
|
+
["_", "n", "f", "f", "f", "f", "f", "n", "_"],
|
|
16
|
+
["_", "f", "f", "n", "f", "n", "f", "f", "_"]
|
|
17
|
+
];
|
|
18
|
+
var MOUTHS = [
|
|
19
|
+
[
|
|
20
|
+
["_", "f", "q", "f", "f", "f", "r", "f", "_"],
|
|
21
|
+
["_", "f", "f", "m", "m", "m", "f", "f", "_"]
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
["_", "f", "q", "f", "f", "f", "f", "f", "_"],
|
|
25
|
+
["_", "f", "f", "m", "m", "m", "f", "f", "_"]
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
["_", "f", "f", "f", "f", "f", "r", "f", "_"],
|
|
29
|
+
["_", "f", "f", "m", "m", "m", "f", "f", "_"]
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
["_", "f", "f", "q", "f", "r", "f", "f", "_"],
|
|
33
|
+
["_", "f", "f", "f", "m", "f", "f", "f", "_"]
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
["_", "q", "f", "f", "f", "f", "f", "r", "_"],
|
|
37
|
+
["_", "f", "m", "m", "m", "m", "m", "f", "_"]
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
["_", "q", "f", "f", "f", "f", "f", "f", "_"],
|
|
41
|
+
["_", "f", "m", "m", "m", "m", "m", "f", "_"]
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
["_", "f", "f", "f", "f", "f", "f", "r", "_"],
|
|
45
|
+
["_", "f", "m", "m", "m", "m", "m", "f", "_"]
|
|
46
|
+
]
|
|
47
|
+
];
|
|
48
|
+
var HATS = [
|
|
49
|
+
[],
|
|
50
|
+
[
|
|
51
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"],
|
|
52
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"],
|
|
53
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"]
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"],
|
|
57
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"]
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
["_", "_", "h", "_", "h", "_", "h", "_", "_"],
|
|
61
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"]
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"],
|
|
65
|
+
["h", "h", "h", "h", "h", "h", "h", "_", "_"]
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
["_", "h", "_", "_", "_", "_", "_", "h", "_"],
|
|
69
|
+
["_", "h", "h", "_", "_", "_", "h", "h", "_"]
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
["_", "_", "_", "_", "h", "_", "_", "_", "_"],
|
|
73
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"]
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
["_", "_", "_", "_", "h", "_", "_", "_", "_"],
|
|
77
|
+
["_", "_", "_", "_", "h", "_", "_", "_", "_"]
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"]
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "_"],
|
|
84
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"]
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"],
|
|
88
|
+
["h", "h", "h", "h", "h", "h", "h", "h", "h"]
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
["_", "_", "_", "_", "k", "_", "_", "_", "_"],
|
|
92
|
+
["_", "_", "_", "_", "h", "_", "_", "_", "_"],
|
|
93
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"]
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
["_", "h", "h", "_", "_", "_", "h", "h", "_"]
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
["_", "h", "_", "h", "_", "h", "_", "h", "_"],
|
|
100
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"]
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
["_", "_", "_", "_", "h", "_", "_", "_", "_"],
|
|
104
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"],
|
|
105
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"]
|
|
106
|
+
],
|
|
107
|
+
[
|
|
108
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"],
|
|
109
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"]
|
|
110
|
+
],
|
|
111
|
+
[
|
|
112
|
+
["_", "_", "k", "h", "h", "h", "k", "_", "_"],
|
|
113
|
+
["_", "k", "h", "h", "h", "h", "h", "k", "_"]
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
["_", "_", "_", "_", "h", "h", "h", "k", "_"],
|
|
117
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"]
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"],
|
|
121
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"],
|
|
122
|
+
["h", "h", "h", "h", "h", "h", "h", "h", "h"]
|
|
123
|
+
],
|
|
124
|
+
[
|
|
125
|
+
["_", "_", "_", "h", "h", "h", "_", "_", "_"],
|
|
126
|
+
["_", "_", "h", "h", "h", "h", "h", "_", "_"],
|
|
127
|
+
["_", "h", "f", "h", "f", "h", "f", "h", "_"]
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
["h", "h", "_", "_", "_", "_", "_", "h", "h"],
|
|
131
|
+
["h", "h", "h", "_", "_", "_", "h", "h", "h"]
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"],
|
|
135
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"],
|
|
136
|
+
["_", "h", "h", "h", "h", "h", "h", "h", "_"],
|
|
137
|
+
["_", "d", "d", "d", "d", "d", "d", "d", "_"],
|
|
138
|
+
["h", "h", "h", "h", "h", "h", "h", "h", "h"]
|
|
139
|
+
]
|
|
140
|
+
];
|
|
141
|
+
var BODIES = [
|
|
142
|
+
[
|
|
143
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "_"],
|
|
144
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "_"]
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
["a", "f", "f", "f", "f", "f", "f", "f", "a"],
|
|
148
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "_"]
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
["_", "_", "f", "f", "f", "f", "f", "_", "_"],
|
|
152
|
+
["_", "_", "f", "f", "f", "f", "f", "_", "_"]
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
["_", "a", "f", "f", "f", "f", "f", "a", "_"],
|
|
156
|
+
["_", "_", "f", "f", "f", "f", "f", "_", "_"]
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "_"],
|
|
160
|
+
["_", "_", "f", "f", "f", "f", "f", "_", "_"]
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
["a", "f", "f", "f", "f", "f", "f", "f", "a"],
|
|
164
|
+
["_", "_", "f", "f", "f", "f", "f", "_", "_"]
|
|
165
|
+
]
|
|
166
|
+
];
|
|
167
|
+
var LEGS = [
|
|
168
|
+
[
|
|
169
|
+
["_", "_", "f", "_", "_", "f", "_", "_", "_"],
|
|
170
|
+
["_", "f", "_", "_", "_", "_", "f", "_", "_"]
|
|
171
|
+
],
|
|
172
|
+
[
|
|
173
|
+
["_", "_", "l", "_", "_", "_", "_", "l", "_"],
|
|
174
|
+
["_", "_", "_", "l", "_", "l", "_", "_", "_"]
|
|
175
|
+
],
|
|
176
|
+
[
|
|
177
|
+
["_", "f", "_", "l", "_", "l", "_", "f", "_"],
|
|
178
|
+
["_", "f", "_", "f", "_", "l", "_", "f", "_"],
|
|
179
|
+
["_", "f", "_", "l", "_", "f", "_", "f", "_"]
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
["_", "_", "l", "_", "_", "_", "l", "_", "_"],
|
|
183
|
+
["_", "_", "_", "l", "_", "l", "_", "_", "_"]
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
["_", "f", "_", "_", "_", "_", "_", "f", "_"],
|
|
187
|
+
["_", "_", "f", "_", "_", "_", "f", "_", "_"]
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
["_", "_", "_", "l", "_", "l", "_", "_", "_"],
|
|
191
|
+
["_", "_", "l", "_", "_", "_", "l", "_", "_"]
|
|
192
|
+
]
|
|
193
|
+
];
|
|
194
|
+
var SLOTS = {
|
|
195
|
+
eyes: 12,
|
|
196
|
+
mouths: 12,
|
|
197
|
+
hats: 24,
|
|
198
|
+
bodies: 8,
|
|
199
|
+
legs: 8,
|
|
200
|
+
hues: 12
|
|
201
|
+
};
|
|
202
|
+
function encodeDNA(traits) {
|
|
203
|
+
let n = traits.hatHue;
|
|
204
|
+
n += traits.faceHue * SLOTS.hues;
|
|
205
|
+
n += traits.legs * SLOTS.hues * SLOTS.hues;
|
|
206
|
+
n += traits.body * SLOTS.legs * SLOTS.hues * SLOTS.hues;
|
|
207
|
+
n += traits.hat * SLOTS.bodies * SLOTS.legs * SLOTS.hues * SLOTS.hues;
|
|
208
|
+
n += traits.mouth * SLOTS.hats * SLOTS.bodies * SLOTS.legs * SLOTS.hues * SLOTS.hues;
|
|
209
|
+
n += traits.eyes * SLOTS.mouths * SLOTS.hats * SLOTS.bodies * SLOTS.legs * SLOTS.hues * SLOTS.hues;
|
|
210
|
+
return n.toString(16).padStart(7, "0");
|
|
211
|
+
}
|
|
212
|
+
function decodeDNA(hex) {
|
|
213
|
+
let n = parseInt(hex, 16);
|
|
214
|
+
const hatHue = n % SLOTS.hues;
|
|
215
|
+
n = Math.floor(n / SLOTS.hues);
|
|
216
|
+
const faceHue = n % SLOTS.hues;
|
|
217
|
+
n = Math.floor(n / SLOTS.hues);
|
|
218
|
+
const legs = n % SLOTS.legs;
|
|
219
|
+
n = Math.floor(n / SLOTS.legs);
|
|
220
|
+
const body = n % SLOTS.bodies;
|
|
221
|
+
n = Math.floor(n / SLOTS.bodies);
|
|
222
|
+
const hat = n % SLOTS.hats;
|
|
223
|
+
n = Math.floor(n / SLOTS.hats);
|
|
224
|
+
const mouth = n % SLOTS.mouths;
|
|
225
|
+
n = Math.floor(n / SLOTS.mouths);
|
|
226
|
+
const eyes = n % SLOTS.eyes;
|
|
227
|
+
return {
|
|
228
|
+
eyes: eyes % EYES.length,
|
|
229
|
+
mouth: mouth % MOUTHS.length,
|
|
230
|
+
hat: hat % HATS.length,
|
|
231
|
+
body: body % BODIES.length,
|
|
232
|
+
legs: legs % LEGS.length,
|
|
233
|
+
faceHue: faceHue % SLOTS.hues,
|
|
234
|
+
hatHue: hatHue % SLOTS.hues
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function traitsFromName(name) {
|
|
238
|
+
let hash = 0;
|
|
239
|
+
for (let i = 0;i < name.length; i++) {
|
|
240
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
241
|
+
}
|
|
242
|
+
hash = Math.imul(hash ^ hash >>> 16, 73244475);
|
|
243
|
+
hash = Math.imul(hash ^ hash >>> 13, 73244475);
|
|
244
|
+
hash = hash ^ hash >>> 16;
|
|
245
|
+
const h = Math.abs(hash);
|
|
246
|
+
return {
|
|
247
|
+
eyes: h % EYES.length,
|
|
248
|
+
mouth: (h >>> 4) % MOUTHS.length,
|
|
249
|
+
hat: (h >>> 8) % HATS.length,
|
|
250
|
+
body: (h >>> 14) % BODIES.length,
|
|
251
|
+
legs: (h >>> 18) % LEGS.length,
|
|
252
|
+
faceHue: (h >>> 22) % 12,
|
|
253
|
+
hatHue: (h >>> 26) % 12
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function generateRandomDNA() {
|
|
257
|
+
return encodeDNA({
|
|
258
|
+
eyes: Math.floor(Math.random() * EYES.length),
|
|
259
|
+
mouth: Math.floor(Math.random() * MOUTHS.length),
|
|
260
|
+
hat: Math.floor(Math.random() * HATS.length),
|
|
261
|
+
body: Math.floor(Math.random() * BODIES.length),
|
|
262
|
+
legs: Math.floor(Math.random() * LEGS.length),
|
|
263
|
+
faceHue: Math.floor(Math.random() * SLOTS.hues),
|
|
264
|
+
hatHue: Math.floor(Math.random() * SLOTS.hues)
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
var WAVE_FRAMES = [
|
|
268
|
+
[
|
|
269
|
+
["a", "f", "f", "f", "f", "f", "f", "f", "_"],
|
|
270
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "a"]
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "a"],
|
|
274
|
+
["a", "f", "f", "f", "f", "f", "f", "f", "_"]
|
|
275
|
+
]
|
|
276
|
+
];
|
|
277
|
+
var TALK_FRAMES = [
|
|
278
|
+
[
|
|
279
|
+
["_", "f", "f", "f", "f", "f", "f", "f", "_"],
|
|
280
|
+
["_", "f", "f", "d", "d", "d", "f", "f", "_"]
|
|
281
|
+
]
|
|
282
|
+
];
|
|
283
|
+
function generateGrid(traits, frame = 0, talkFrame = 0, waveFrame = 0) {
|
|
284
|
+
const legFrames = LEGS[traits.legs];
|
|
285
|
+
const legRow = legFrames[frame % legFrames.length];
|
|
286
|
+
const mouthRows = talkFrame === 0 ? MOUTHS[traits.mouth] : TALK_FRAMES[(talkFrame - 1) % TALK_FRAMES.length];
|
|
287
|
+
const bodyRows = waveFrame === 0 ? BODIES[traits.body] : WAVE_FRAMES[(waveFrame - 1) % WAVE_FRAMES.length];
|
|
288
|
+
return [
|
|
289
|
+
...HATS[traits.hat],
|
|
290
|
+
F,
|
|
291
|
+
EYES[traits.eyes],
|
|
292
|
+
...mouthRows,
|
|
293
|
+
...bodyRows,
|
|
294
|
+
legRow
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
function hslToRgb(h, s, l) {
|
|
298
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
299
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
300
|
+
const m = l - c / 2;
|
|
301
|
+
let r1, g1, b1;
|
|
302
|
+
if (h < 60) {
|
|
303
|
+
[r1, g1, b1] = [c, x, 0];
|
|
304
|
+
} else if (h < 120) {
|
|
305
|
+
[r1, g1, b1] = [x, c, 0];
|
|
306
|
+
} else if (h < 180) {
|
|
307
|
+
[r1, g1, b1] = [0, c, x];
|
|
308
|
+
} else if (h < 240) {
|
|
309
|
+
[r1, g1, b1] = [0, x, c];
|
|
310
|
+
} else if (h < 300) {
|
|
311
|
+
[r1, g1, b1] = [x, 0, c];
|
|
312
|
+
} else {
|
|
313
|
+
[r1, g1, b1] = [c, 0, x];
|
|
314
|
+
}
|
|
315
|
+
return [
|
|
316
|
+
Math.round((r1 + m) * 255),
|
|
317
|
+
Math.round((g1 + m) * 255),
|
|
318
|
+
Math.round((b1 + m) * 255)
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
function renderSVG(dna, pixelSize = 10, frame = 0, background = "#000") {
|
|
322
|
+
const traits = decodeDNA(dna);
|
|
323
|
+
const grid = generateGrid(traits, frame);
|
|
324
|
+
const faceHueDeg = traits.faceHue * 30;
|
|
325
|
+
const hatHueDeg = traits.hatHue * 30;
|
|
326
|
+
const faceRgb = hslToRgb(faceHueDeg, 0.5, 0.5);
|
|
327
|
+
const darkRgb = hslToRgb(faceHueDeg, 0.5, 0.28);
|
|
328
|
+
const hatRgb = hslToRgb(hatHueDeg, 0.5, 0.5);
|
|
329
|
+
const toHex = (r, g, b) => `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
330
|
+
const faceHex = toHex(...faceRgb);
|
|
331
|
+
const darkHex = toHex(...darkRgb);
|
|
332
|
+
const hatHex = toHex(...hatRgb);
|
|
333
|
+
const cols = 9;
|
|
334
|
+
const rows = grid.length;
|
|
335
|
+
const pad = 1;
|
|
336
|
+
const w = (cols + pad * 2) * pixelSize;
|
|
337
|
+
const h = (rows + pad * 2) * pixelSize;
|
|
338
|
+
const half = Math.round(pixelSize / 2);
|
|
339
|
+
const quarter = Math.round(pixelSize / 4);
|
|
340
|
+
const rects = [];
|
|
341
|
+
for (let y = 0;y < rows; y++) {
|
|
342
|
+
for (let x = 0;x < cols; x++) {
|
|
343
|
+
const cell = grid[y][x];
|
|
344
|
+
const rx = (x + pad) * pixelSize;
|
|
345
|
+
const ry = (y + pad) * pixelSize;
|
|
346
|
+
if (cell === "f") {
|
|
347
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
348
|
+
} else if (cell === "l") {
|
|
349
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${half}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
350
|
+
} else if (cell === "a") {
|
|
351
|
+
rects.push(`<rect x="${rx}" y="${ry + half}" width="${pixelSize}" height="${half}" fill="${faceHex}"/>`);
|
|
352
|
+
} else if (cell === "e" || cell === "d") {
|
|
353
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${darkHex}"/>`);
|
|
354
|
+
} else if (cell === "s") {
|
|
355
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
356
|
+
rects.push(`<rect x="${rx}" y="${ry + half}" width="${pixelSize}" height="${half}" fill="${darkHex}"/>`);
|
|
357
|
+
} else if (cell === "n") {
|
|
358
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
359
|
+
rects.push(`<rect x="${rx + quarter}" y="${ry}" width="${half}" height="${pixelSize}" fill="${darkHex}"/>`);
|
|
360
|
+
} else if (cell === "m") {
|
|
361
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
362
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${half}" fill="${darkHex}"/>`);
|
|
363
|
+
} else if (cell === "q") {
|
|
364
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
365
|
+
rects.push(`<rect x="${rx + half}" y="${ry + half}" width="${half}" height="${half}" fill="${darkHex}"/>`);
|
|
366
|
+
} else if (cell === "r") {
|
|
367
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
|
|
368
|
+
rects.push(`<rect x="${rx}" y="${ry + half}" width="${half}" height="${half}" fill="${darkHex}"/>`);
|
|
369
|
+
} else if (cell === "h") {
|
|
370
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${hatHex}"/>`);
|
|
371
|
+
} else if (cell === "k") {
|
|
372
|
+
rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${hatHex}"/>`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const bg = background ? `<rect width="${w}" height="${h}" fill="${background}"/>
|
|
377
|
+
` : "";
|
|
378
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" shape-rendering="crispEdges">
|
|
379
|
+
${bg}${rects.join(`
|
|
380
|
+
`)}
|
|
381
|
+
</svg>`;
|
|
382
|
+
}
|
|
383
|
+
function renderTerminal(dna, frame = 0) {
|
|
384
|
+
const traits = decodeDNA(dna);
|
|
385
|
+
const grid = generateGrid(traits, frame);
|
|
386
|
+
const faceHueDeg = traits.faceHue * 30;
|
|
387
|
+
const hatHueDeg = traits.hatHue * 30;
|
|
388
|
+
const faceRgb = hslToRgb(faceHueDeg, 0.5, 0.5);
|
|
389
|
+
const darkRgb = hslToRgb(faceHueDeg, 0.5, 0.28);
|
|
390
|
+
const hatRgb = hslToRgb(hatHueDeg, 0.5, 0.5);
|
|
391
|
+
const faceAnsi = `\x1B[38;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
|
|
392
|
+
const darkAnsi = `\x1B[38;2;${darkRgb[0]};${darkRgb[1]};${darkRgb[2]}m`;
|
|
393
|
+
const hatAnsi = `\x1B[38;2;${hatRgb[0]};${hatRgb[1]};${hatRgb[2]}m`;
|
|
394
|
+
const reset = "\x1B[0m";
|
|
395
|
+
const faceBg = `\x1B[48;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
|
|
396
|
+
const lines = [];
|
|
397
|
+
for (const row of grid) {
|
|
398
|
+
let line = "";
|
|
399
|
+
for (const cell of row) {
|
|
400
|
+
if (cell === "_") {
|
|
401
|
+
line += " ";
|
|
402
|
+
} else if (cell === "f") {
|
|
403
|
+
line += `${faceAnsi}██${reset}`;
|
|
404
|
+
} else if (cell === "l") {
|
|
405
|
+
line += `${faceAnsi}▌${reset} `;
|
|
406
|
+
} else if (cell === "e" || cell === "d") {
|
|
407
|
+
line += `${darkAnsi}██${reset}`;
|
|
408
|
+
} else if (cell === "s") {
|
|
409
|
+
line += `${darkAnsi}${faceBg}▄▄${reset}`;
|
|
410
|
+
} else if (cell === "n") {
|
|
411
|
+
line += `${darkAnsi}${faceBg}▐▌${reset}`;
|
|
412
|
+
} else if (cell === "m") {
|
|
413
|
+
line += `${darkAnsi}${faceBg}▀▀${reset}`;
|
|
414
|
+
} else if (cell === "q") {
|
|
415
|
+
line += `${darkAnsi}${faceBg} ▗${reset}`;
|
|
416
|
+
} else if (cell === "r") {
|
|
417
|
+
line += `${darkAnsi}${faceBg}▖ ${reset}`;
|
|
418
|
+
} else if (cell === "a") {
|
|
419
|
+
line += `${faceAnsi}▄▄${reset}`;
|
|
420
|
+
} else if (cell === "h") {
|
|
421
|
+
line += `${hatAnsi}██${reset}`;
|
|
422
|
+
} else if (cell === "k") {
|
|
423
|
+
line += `${hatAnsi}▐▌${reset}`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
lines.push(line);
|
|
427
|
+
}
|
|
428
|
+
return lines.join(`
|
|
429
|
+
`);
|
|
430
|
+
}
|
|
431
|
+
function renderTerminalSmall(dna, frame = 0) {
|
|
432
|
+
const traits = decodeDNA(dna);
|
|
433
|
+
const grid = generateGrid(traits, frame);
|
|
434
|
+
const faceHueDeg = traits.faceHue * 30;
|
|
435
|
+
const hatHueDeg = traits.hatHue * 30;
|
|
436
|
+
const faceRgb = hslToRgb(faceHueDeg, 0.5, 0.5);
|
|
437
|
+
const darkRgb = hslToRgb(faceHueDeg, 0.5, 0.28);
|
|
438
|
+
const hatRgb = hslToRgb(hatHueDeg, 0.5, 0.5);
|
|
439
|
+
function cellRgb(cell) {
|
|
440
|
+
if (cell === "f" || cell === "l" || cell === "a" || cell === "q" || cell === "r")
|
|
441
|
+
return faceRgb;
|
|
442
|
+
if (cell === "e" || cell === "s" || cell === "n" || cell === "d" || cell === "m")
|
|
443
|
+
return darkRgb;
|
|
444
|
+
if (cell === "h" || cell === "k")
|
|
445
|
+
return hatRgb;
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
const reset = "\x1B[0m";
|
|
449
|
+
const lines = [];
|
|
450
|
+
for (let r = 0;r < grid.length; r += 2) {
|
|
451
|
+
const topRow = grid[r];
|
|
452
|
+
const botRow = r + 1 < grid.length ? grid[r + 1] : null;
|
|
453
|
+
let line = "";
|
|
454
|
+
for (let c = 0;c < topRow.length; c++) {
|
|
455
|
+
const top = cellRgb(topRow[c]);
|
|
456
|
+
const bot = botRow ? cellRgb(botRow[c]) : null;
|
|
457
|
+
if (top && bot) {
|
|
458
|
+
line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m\x1B[48;2;${bot[0]};${bot[1]};${bot[2]}m▀${reset}`;
|
|
459
|
+
} else if (top) {
|
|
460
|
+
line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m▀${reset}`;
|
|
461
|
+
} else if (bot) {
|
|
462
|
+
line += `\x1B[38;2;${bot[0]};${bot[1]};${bot[2]}m▄${reset}`;
|
|
463
|
+
} else {
|
|
464
|
+
line += " ";
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
lines.push(line);
|
|
468
|
+
}
|
|
469
|
+
return lines.join(`
|
|
470
|
+
`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// packages/termlings/src/cli.ts
|
|
474
|
+
var args = process.argv.slice(2);
|
|
475
|
+
var flags = new Set;
|
|
476
|
+
var input;
|
|
477
|
+
for (const arg of args) {
|
|
478
|
+
if (arg.startsWith("--")) {
|
|
479
|
+
flags.add(arg.slice(2));
|
|
480
|
+
} else if (arg === "-h") {
|
|
481
|
+
flags.add("help");
|
|
482
|
+
} else if (!input) {
|
|
483
|
+
input = arg;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (flags.has("help") || flags.has("h")) {
|
|
487
|
+
console.log(`Usage: termlings [dna-or-name] [options]
|
|
488
|
+
|
|
489
|
+
Render a termling in your terminal.
|
|
490
|
+
|
|
491
|
+
Arguments:
|
|
492
|
+
<dna> 7-char hex DNA string (e.g. 0a3f201)
|
|
493
|
+
<name> Any string — generates deterministic avatar
|
|
494
|
+
|
|
495
|
+
Options:
|
|
496
|
+
--walk Animate walking
|
|
497
|
+
--talk Animate talking
|
|
498
|
+
--wave Animate waving
|
|
499
|
+
--compact Half-height rendering
|
|
500
|
+
--random Generate a random termling
|
|
501
|
+
--svg Output SVG instead of terminal art
|
|
502
|
+
--info Show DNA traits info
|
|
503
|
+
--help Show this help`);
|
|
504
|
+
process.exit(0);
|
|
505
|
+
}
|
|
506
|
+
if (flags.has("random") || !input) {
|
|
507
|
+
input = generateRandomDNA();
|
|
508
|
+
}
|
|
509
|
+
var isDNA = /^[0-9a-f]{6,7}$/i.test(input);
|
|
510
|
+
var dna = isDNA ? input : encodeDNA(traitsFromName(input));
|
|
511
|
+
var DIM = "\x1B[2m";
|
|
512
|
+
var RESET = "\x1B[0m";
|
|
513
|
+
if (!isDNA) {
|
|
514
|
+
console.log(`${DIM}"${input}" → dna: ${dna}${RESET}
|
|
515
|
+
`);
|
|
516
|
+
} else {
|
|
517
|
+
console.log(`${DIM}dna: ${dna}${RESET}
|
|
518
|
+
`);
|
|
519
|
+
}
|
|
520
|
+
if (flags.has("info")) {
|
|
521
|
+
const traits = decodeDNA(dna);
|
|
522
|
+
console.log("Traits:", JSON.stringify(traits, null, 2));
|
|
523
|
+
console.log();
|
|
524
|
+
}
|
|
525
|
+
if (flags.has("svg")) {
|
|
526
|
+
console.log(renderSVG(dna, 10, 0, null));
|
|
527
|
+
process.exit(0);
|
|
528
|
+
}
|
|
529
|
+
var animate = flags.has("walk") || flags.has("talk") || flags.has("wave");
|
|
530
|
+
var compact = flags.has("compact");
|
|
531
|
+
if (!animate) {
|
|
532
|
+
const output = compact ? renderTerminalSmall(dna) : renderTerminal(dna);
|
|
533
|
+
console.log(output);
|
|
534
|
+
process.exit(0);
|
|
535
|
+
}
|
|
536
|
+
var traits = decodeDNA(dna);
|
|
537
|
+
var legFrameCount = LEGS[traits.legs].length;
|
|
538
|
+
var faceRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.5);
|
|
539
|
+
var darkRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.28);
|
|
540
|
+
var hatRgb = hslToRgb(traits.hatHue * 30, 0.5, 0.5);
|
|
541
|
+
var walkFrame = 0;
|
|
542
|
+
var talkFrame = 0;
|
|
543
|
+
var waveFrame = 0;
|
|
544
|
+
var tick = 0;
|
|
545
|
+
process.stdout.write("\x1B[?25l");
|
|
546
|
+
function cleanup() {
|
|
547
|
+
process.stdout.write(`\x1B[?25h
|
|
548
|
+
`);
|
|
549
|
+
process.exit(0);
|
|
550
|
+
}
|
|
551
|
+
process.on("SIGINT", cleanup);
|
|
552
|
+
process.on("SIGTERM", cleanup);
|
|
553
|
+
var firstGrid = generateGrid(traits, 0, 0, 0);
|
|
554
|
+
var firstOutput = compact ? renderSmallFromGrid(firstGrid) : renderFromGrid(firstGrid);
|
|
555
|
+
var lineCount = firstOutput.split(`
|
|
556
|
+
`).length;
|
|
557
|
+
process.stdout.write(firstOutput + `
|
|
558
|
+
`);
|
|
559
|
+
setInterval(() => {
|
|
560
|
+
tick++;
|
|
561
|
+
if (flags.has("walk"))
|
|
562
|
+
walkFrame = tick % legFrameCount;
|
|
563
|
+
if (flags.has("talk"))
|
|
564
|
+
talkFrame = tick % 2;
|
|
565
|
+
if (flags.has("wave"))
|
|
566
|
+
waveFrame = tick % 2 + 1;
|
|
567
|
+
const grid = generateGrid(traits, walkFrame, talkFrame, waveFrame);
|
|
568
|
+
const output = compact ? renderSmallFromGrid(grid) : renderFromGrid(grid);
|
|
569
|
+
process.stdout.write(`\x1B[${lineCount}A`);
|
|
570
|
+
process.stdout.write(output + `
|
|
571
|
+
`);
|
|
572
|
+
}, 300);
|
|
573
|
+
function renderFromGrid(grid) {
|
|
574
|
+
const faceAnsi = `\x1B[38;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
|
|
575
|
+
const darkAnsi = `\x1B[38;2;${darkRgb[0]};${darkRgb[1]};${darkRgb[2]}m`;
|
|
576
|
+
const hatAnsi = `\x1B[38;2;${hatRgb[0]};${hatRgb[1]};${hatRgb[2]}m`;
|
|
577
|
+
const reset = "\x1B[0m";
|
|
578
|
+
const faceBg = `\x1B[48;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
|
|
579
|
+
const lines = [];
|
|
580
|
+
for (const row of grid) {
|
|
581
|
+
let line = "";
|
|
582
|
+
for (const cell of row) {
|
|
583
|
+
if (cell === "_")
|
|
584
|
+
line += " ";
|
|
585
|
+
else if (cell === "f")
|
|
586
|
+
line += `${faceAnsi}██${reset}`;
|
|
587
|
+
else if (cell === "l")
|
|
588
|
+
line += `${faceAnsi}▌${reset} `;
|
|
589
|
+
else if (cell === "e" || cell === "d")
|
|
590
|
+
line += `${darkAnsi}██${reset}`;
|
|
591
|
+
else if (cell === "s")
|
|
592
|
+
line += `${darkAnsi}${faceBg}▄▄${reset}`;
|
|
593
|
+
else if (cell === "n")
|
|
594
|
+
line += `${darkAnsi}${faceBg}▐▌${reset}`;
|
|
595
|
+
else if (cell === "m")
|
|
596
|
+
line += `${darkAnsi}${faceBg}▀▀${reset}`;
|
|
597
|
+
else if (cell === "q")
|
|
598
|
+
line += `${darkAnsi}${faceBg} ▗${reset}`;
|
|
599
|
+
else if (cell === "r")
|
|
600
|
+
line += `${darkAnsi}${faceBg}▖ ${reset}`;
|
|
601
|
+
else if (cell === "a")
|
|
602
|
+
line += `${faceAnsi}▄▄${reset}`;
|
|
603
|
+
else if (cell === "h")
|
|
604
|
+
line += `${hatAnsi}██${reset}`;
|
|
605
|
+
else if (cell === "k")
|
|
606
|
+
line += `${hatAnsi}▐▌${reset}`;
|
|
607
|
+
}
|
|
608
|
+
lines.push(line);
|
|
609
|
+
}
|
|
610
|
+
return lines.join(`
|
|
611
|
+
`);
|
|
612
|
+
}
|
|
613
|
+
function renderSmallFromGrid(grid) {
|
|
614
|
+
const reset = "\x1B[0m";
|
|
615
|
+
function cellRgb(cell) {
|
|
616
|
+
if (cell === "f" || cell === "l" || cell === "a" || cell === "q" || cell === "r")
|
|
617
|
+
return faceRgb;
|
|
618
|
+
if (cell === "e" || cell === "s" || cell === "n" || cell === "d" || cell === "m")
|
|
619
|
+
return darkRgb;
|
|
620
|
+
if (cell === "h" || cell === "k")
|
|
621
|
+
return hatRgb;
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
const lines = [];
|
|
625
|
+
for (let r = 0;r < grid.length; r += 2) {
|
|
626
|
+
const topRow = grid[r];
|
|
627
|
+
const botRow = r + 1 < grid.length ? grid[r + 1] : null;
|
|
628
|
+
let line = "";
|
|
629
|
+
for (let c = 0;c < topRow.length; c++) {
|
|
630
|
+
const top = cellRgb(topRow[c]);
|
|
631
|
+
const bot = botRow ? cellRgb(botRow[c]) : null;
|
|
632
|
+
if (top && bot) {
|
|
633
|
+
line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m\x1B[48;2;${bot[0]};${bot[1]};${bot[2]}m▀${reset}`;
|
|
634
|
+
} else if (top) {
|
|
635
|
+
line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m▀${reset}`;
|
|
636
|
+
} else if (bot) {
|
|
637
|
+
line += `\x1B[38;2;${bot[0]};${bot[1]};${bot[2]}m▄${reset}`;
|
|
638
|
+
} else {
|
|
639
|
+
line += " ";
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
lines.push(line);
|
|
643
|
+
}
|
|
644
|
+
return lines.join(`
|
|
645
|
+
`);
|
|
646
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termlings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Pixel art avatar system. 32M+ unique characters from a 7-char hex DNA string.",
|
|
7
|
+
"bin": {
|
|
8
|
+
"termlings": "./bin/termlings.js"
|
|
9
|
+
},
|
|
7
10
|
"repository": {
|
|
8
11
|
"type": "git",
|
|
9
12
|
"url": "https://github.com/tomtev/touchgrass",
|
|
10
|
-
"directory": "packages/
|
|
13
|
+
"directory": "packages/termlings"
|
|
11
14
|
},
|
|
12
|
-
"keywords": ["avatar", "pixel-art", "svelte", "react", "vue", "terminal", "ink"],
|
|
15
|
+
"keywords": ["avatar", "pixel-art", "svelte", "react", "vue", "terminal", "ink", "cli"],
|
|
13
16
|
"exports": {
|
|
14
17
|
".": "./src/index.ts",
|
|
15
18
|
"./svelte": "./src/svelte/index.ts",
|
|
@@ -17,5 +20,5 @@
|
|
|
17
20
|
"./vue": "./src/vue/index.ts",
|
|
18
21
|
"./ink": "./src/ink/index.ts"
|
|
19
22
|
},
|
|
20
|
-
"files": ["src"]
|
|
23
|
+
"files": ["src", "bin"]
|
|
21
24
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
renderTerminal,
|
|
4
|
+
renderTerminalSmall,
|
|
5
|
+
renderSVG,
|
|
6
|
+
decodeDNA,
|
|
7
|
+
encodeDNA,
|
|
8
|
+
generateRandomDNA,
|
|
9
|
+
traitsFromName,
|
|
10
|
+
generateGrid,
|
|
11
|
+
hslToRgb,
|
|
12
|
+
LEGS,
|
|
13
|
+
} from "./index.js";
|
|
14
|
+
import type { Pixel } from "./index.js";
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const flags = new Set<string>();
|
|
18
|
+
let input: string | undefined;
|
|
19
|
+
|
|
20
|
+
for (const arg of args) {
|
|
21
|
+
if (arg.startsWith("--")) {
|
|
22
|
+
flags.add(arg.slice(2));
|
|
23
|
+
} else if (arg === "-h") {
|
|
24
|
+
flags.add("help");
|
|
25
|
+
} else if (!input) {
|
|
26
|
+
input = arg;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (flags.has("help") || flags.has("h")) {
|
|
31
|
+
console.log(`Usage: termlings [dna-or-name] [options]
|
|
32
|
+
|
|
33
|
+
Render a termling in your terminal.
|
|
34
|
+
|
|
35
|
+
Arguments:
|
|
36
|
+
<dna> 7-char hex DNA string (e.g. 0a3f201)
|
|
37
|
+
<name> Any string — generates deterministic avatar
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--walk Animate walking
|
|
41
|
+
--talk Animate talking
|
|
42
|
+
--wave Animate waving
|
|
43
|
+
--compact Half-height rendering
|
|
44
|
+
--random Generate a random termling
|
|
45
|
+
--svg Output SVG instead of terminal art
|
|
46
|
+
--info Show DNA traits info
|
|
47
|
+
--help Show this help`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (flags.has("random") || !input) {
|
|
52
|
+
input = generateRandomDNA();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Resolve DNA: hex string or name
|
|
56
|
+
const isDNA = /^[0-9a-f]{6,7}$/i.test(input);
|
|
57
|
+
const dna = isDNA ? input : encodeDNA(traitsFromName(input));
|
|
58
|
+
|
|
59
|
+
const DIM = "\x1b[2m";
|
|
60
|
+
const RESET = "\x1b[0m";
|
|
61
|
+
|
|
62
|
+
if (!isDNA) {
|
|
63
|
+
console.log(`${DIM}"${input}" → dna: ${dna}${RESET}\n`);
|
|
64
|
+
} else {
|
|
65
|
+
console.log(`${DIM}dna: ${dna}${RESET}\n`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (flags.has("info")) {
|
|
69
|
+
const traits = decodeDNA(dna);
|
|
70
|
+
console.log("Traits:", JSON.stringify(traits, null, 2));
|
|
71
|
+
console.log();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (flags.has("svg")) {
|
|
75
|
+
console.log(renderSVG(dna, 10, 0, null));
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const animate = flags.has("walk") || flags.has("talk") || flags.has("wave");
|
|
80
|
+
const compact = flags.has("compact");
|
|
81
|
+
|
|
82
|
+
if (!animate) {
|
|
83
|
+
// Static render using ANSI colors
|
|
84
|
+
const output = compact ? renderTerminalSmall(dna) : renderTerminal(dna);
|
|
85
|
+
console.log(output);
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Animated render with ANSI escape codes ---
|
|
90
|
+
const traits = decodeDNA(dna);
|
|
91
|
+
const legFrameCount = LEGS[traits.legs].length;
|
|
92
|
+
const faceRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.5);
|
|
93
|
+
const darkRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.28);
|
|
94
|
+
const hatRgb = hslToRgb(traits.hatHue * 30, 0.5, 0.5);
|
|
95
|
+
|
|
96
|
+
let walkFrame = 0;
|
|
97
|
+
let talkFrame = 0;
|
|
98
|
+
let waveFrame = 0;
|
|
99
|
+
let tick = 0;
|
|
100
|
+
|
|
101
|
+
// Hide cursor
|
|
102
|
+
process.stdout.write("\x1b[?25l");
|
|
103
|
+
|
|
104
|
+
function cleanup() {
|
|
105
|
+
process.stdout.write("\x1b[?25h\n");
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
process.on("SIGINT", cleanup);
|
|
109
|
+
process.on("SIGTERM", cleanup);
|
|
110
|
+
|
|
111
|
+
// Draw first frame
|
|
112
|
+
const firstGrid = generateGrid(traits, 0, 0, 0);
|
|
113
|
+
const firstOutput = compact
|
|
114
|
+
? renderSmallFromGrid(firstGrid)
|
|
115
|
+
: renderFromGrid(firstGrid);
|
|
116
|
+
const lineCount = firstOutput.split("\n").length;
|
|
117
|
+
process.stdout.write(firstOutput + "\n");
|
|
118
|
+
|
|
119
|
+
setInterval(() => {
|
|
120
|
+
tick++;
|
|
121
|
+
|
|
122
|
+
if (flags.has("walk")) walkFrame = tick % legFrameCount;
|
|
123
|
+
if (flags.has("talk")) talkFrame = tick % 2;
|
|
124
|
+
if (flags.has("wave")) waveFrame = (tick % 2) + 1;
|
|
125
|
+
|
|
126
|
+
const grid = generateGrid(traits, walkFrame, talkFrame, waveFrame);
|
|
127
|
+
const output = compact
|
|
128
|
+
? renderSmallFromGrid(grid)
|
|
129
|
+
: renderFromGrid(grid);
|
|
130
|
+
|
|
131
|
+
// Move cursor up to overwrite previous frame
|
|
132
|
+
process.stdout.write(`\x1b[${lineCount}A`);
|
|
133
|
+
process.stdout.write(output + "\n");
|
|
134
|
+
}, 300);
|
|
135
|
+
|
|
136
|
+
function renderFromGrid(grid: Pixel[][]): string {
|
|
137
|
+
const faceAnsi = `\x1b[38;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
|
|
138
|
+
const darkAnsi = `\x1b[38;2;${darkRgb[0]};${darkRgb[1]};${darkRgb[2]}m`;
|
|
139
|
+
const hatAnsi = `\x1b[38;2;${hatRgb[0]};${hatRgb[1]};${hatRgb[2]}m`;
|
|
140
|
+
const reset = "\x1b[0m";
|
|
141
|
+
const faceBg = `\x1b[48;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
|
|
142
|
+
|
|
143
|
+
const lines: string[] = [];
|
|
144
|
+
for (const row of grid) {
|
|
145
|
+
let line = "";
|
|
146
|
+
for (const cell of row) {
|
|
147
|
+
if (cell === "_") line += " ";
|
|
148
|
+
else if (cell === "f") line += `${faceAnsi}██${reset}`;
|
|
149
|
+
else if (cell === "l") line += `${faceAnsi}▌${reset} `;
|
|
150
|
+
else if (cell === "e" || cell === "d") line += `${darkAnsi}██${reset}`;
|
|
151
|
+
else if (cell === "s") line += `${darkAnsi}${faceBg}▄▄${reset}`;
|
|
152
|
+
else if (cell === "n") line += `${darkAnsi}${faceBg}▐▌${reset}`;
|
|
153
|
+
else if (cell === "m") line += `${darkAnsi}${faceBg}▀▀${reset}`;
|
|
154
|
+
else if (cell === "q") line += `${darkAnsi}${faceBg} ▗${reset}`;
|
|
155
|
+
else if (cell === "r") line += `${darkAnsi}${faceBg}▖ ${reset}`;
|
|
156
|
+
else if (cell === "a") line += `${faceAnsi}▄▄${reset}`;
|
|
157
|
+
else if (cell === "h") line += `${hatAnsi}██${reset}`;
|
|
158
|
+
else if (cell === "k") line += `${hatAnsi}▐▌${reset}`;
|
|
159
|
+
}
|
|
160
|
+
lines.push(line);
|
|
161
|
+
}
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function renderSmallFromGrid(grid: Pixel[][]): string {
|
|
166
|
+
const reset = "\x1b[0m";
|
|
167
|
+
|
|
168
|
+
function cellRgb(cell: Pixel): [number, number, number] | null {
|
|
169
|
+
if (cell === "f" || cell === "l" || cell === "a" || cell === "q" || cell === "r") return faceRgb;
|
|
170
|
+
if (cell === "e" || cell === "s" || cell === "n" || cell === "d" || cell === "m") return darkRgb;
|
|
171
|
+
if (cell === "h" || cell === "k") return hatRgb;
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const lines: string[] = [];
|
|
176
|
+
for (let r = 0; r < grid.length; r += 2) {
|
|
177
|
+
const topRow = grid[r];
|
|
178
|
+
const botRow = r + 1 < grid.length ? grid[r + 1] : null;
|
|
179
|
+
let line = "";
|
|
180
|
+
for (let c = 0; c < topRow.length; c++) {
|
|
181
|
+
const top = cellRgb(topRow[c]);
|
|
182
|
+
const bot = botRow ? cellRgb(botRow[c]) : null;
|
|
183
|
+
if (top && bot) {
|
|
184
|
+
line += `\x1b[38;2;${top[0]};${top[1]};${top[2]}m\x1b[48;2;${bot[0]};${bot[1]};${bot[2]}m▀${reset}`;
|
|
185
|
+
} else if (top) {
|
|
186
|
+
line += `\x1b[38;2;${top[0]};${top[1]};${top[2]}m▀${reset}`;
|
|
187
|
+
} else if (bot) {
|
|
188
|
+
line += `\x1b[38;2;${bot[0]};${bot[1]};${bot[2]}m▄${reset}`;
|
|
189
|
+
} else {
|
|
190
|
+
line += " ";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
lines.push(line);
|
|
194
|
+
}
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|