@siegesailor/terminal-sigma 1.3.0 → 1.4.1
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/README.md +81 -37
- package/build/app.js +241 -95
- package/build/app.js.map +1 -1
- package/build/components/autocomplete-input.js +57 -0
- package/build/components/autocomplete-input.js.map +1 -0
- package/build/components/character.js +7 -4
- package/build/components/character.js.map +1 -1
- package/build/components/choice.js +11 -6
- package/build/components/choice.js.map +1 -1
- package/build/components/entry-form.js +43 -11
- package/build/components/entry-form.js.map +1 -1
- package/build/components/logs-view.js +9 -3
- package/build/components/logs-view.js.map +1 -1
- package/build/components/multi-choice.js +45 -0
- package/build/components/multi-choice.js.map +1 -0
- package/build/components/today-panel.js +17 -10
- package/build/components/today-panel.js.map +1 -1
- package/build/content.js +198 -66
- package/build/content.js.map +1 -1
- package/build/foods.js +159 -0
- package/build/foods.js.map +1 -0
- package/build/i18n.js +262 -36
- package/build/i18n.js.map +1 -1
- package/build/pixel-art.js +333 -413
- package/build/pixel-art.js.map +1 -1
- package/build/progress.js +33 -0
- package/build/progress.js.map +1 -1
- package/build/quotes.js +404 -220
- package/build/quotes.js.map +1 -1
- package/package.json +1 -1
- package/build/components/menu-grid.js +0 -44
- package/build/components/menu-grid.js.map +0 -1
package/build/pixel-art.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
// The
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
export const stageHeight =
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
build: "average",
|
|
8
|
-
gender: "other",
|
|
9
|
-
};
|
|
10
|
-
// 145 cm draws 20 pixels tall and 205 cm 26; the build follows the BMI.
|
|
11
|
-
export function bodyOf(profile) {
|
|
1
|
+
// The pet lives on a stage as wide as its panel and 28 pixels tall. Each
|
|
2
|
+
// terminal cell draws 2 stacked pixels with "▀", foreground for the top and
|
|
3
|
+
// background for the bottom, so the stage takes 14 rows.
|
|
4
|
+
export const stageHeight = 28;
|
|
5
|
+
export const defaultLook = { size: 1, build: "average", gender: "other" };
|
|
6
|
+
export function lookOf(profile) {
|
|
12
7
|
if (!profile) {
|
|
13
|
-
return
|
|
8
|
+
return defaultLook;
|
|
14
9
|
}
|
|
15
10
|
const bmi = profile.weight / (profile.height / 100) ** 2;
|
|
16
11
|
let build = "average";
|
|
@@ -21,249 +16,152 @@ export function bodyOf(profile) {
|
|
|
21
16
|
build = "broad";
|
|
22
17
|
}
|
|
23
18
|
return {
|
|
24
|
-
|
|
19
|
+
size: Math.min(Math.max(Math.floor((profile.height - 150) / 12), 0), 3),
|
|
25
20
|
build,
|
|
26
21
|
gender: profile.gender,
|
|
22
|
+
goal: profile.goal,
|
|
27
23
|
};
|
|
28
24
|
}
|
|
25
|
+
export const noBadges = { meal: false, workout: false, focus: false };
|
|
29
26
|
// Each equipment slot upgrades once from level 2 and again 4 levels later,
|
|
30
27
|
// staggered so every level from 2 to 9 changes exactly 1 piece.
|
|
31
28
|
export const gearOf = (level) => {
|
|
32
29
|
const tier = (slot) => Math.min(Math.max(Math.floor((level - 2 - slot) / 4) + 1, 0), 2);
|
|
33
|
-
return { gadget: tier(0),
|
|
30
|
+
return { gadget: tier(0), wear: tier(1), headwear: tier(2), aura: tier(3) };
|
|
34
31
|
};
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const right = Math.floor(width / 2) - 1;
|
|
44
|
-
return {
|
|
45
|
-
legWidth,
|
|
46
|
-
frontHip: right - legWidth + 1,
|
|
47
|
-
backHip: -Math.ceil(width / 2),
|
|
48
|
-
legs,
|
|
49
|
-
torso,
|
|
50
|
-
left: -Math.ceil(width / 2),
|
|
51
|
-
right: Math.floor(width / 2) - 1,
|
|
52
|
-
shoulder: legs + torso,
|
|
53
|
-
neck: legs + torso + 1,
|
|
54
|
-
arm: torso - 1,
|
|
55
|
-
};
|
|
32
|
+
// The pet grows twice: a hatchling below level 4, an adult from level 7.
|
|
33
|
+
export const growthOf = (level) => level >= 7 ? 2 : level >= 4 ? 1 : 0;
|
|
34
|
+
const widthOf = { slim: 13, average: 15, broad: 18 };
|
|
35
|
+
const shapeOf = (look, level, squash = 0) => {
|
|
36
|
+
const growth = growthOf(level) - 1;
|
|
37
|
+
const width = widthOf[look.build] + growth * 2 - squash;
|
|
38
|
+
const height = 9 + look.size + growth * 2 + squash;
|
|
39
|
+
return { width, height, top: height + 1 };
|
|
56
40
|
};
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
points.push([
|
|
62
|
-
Math.round(x0 + ((x1 - x0) * step) / steps),
|
|
63
|
-
Math.round(y0 + ((y1 - y0) * step) / steps),
|
|
64
|
-
]);
|
|
65
|
-
}
|
|
66
|
-
return points;
|
|
41
|
+
const span = (put, from, to, y, key) => {
|
|
42
|
+
for (let x = from; x <= to; x++) {
|
|
43
|
+
put(x, y, key);
|
|
44
|
+
}
|
|
67
45
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
const walkFeet = [
|
|
76
|
-
[
|
|
77
|
-
[2, 0],
|
|
78
|
-
[-1, 0],
|
|
79
|
-
],
|
|
80
|
-
[
|
|
81
|
-
[0, 0],
|
|
82
|
-
[0, 1],
|
|
83
|
-
],
|
|
84
|
-
[
|
|
85
|
-
[-3, 0],
|
|
86
|
-
[4, 0],
|
|
87
|
-
],
|
|
88
|
-
[
|
|
89
|
-
[-2, 1],
|
|
90
|
-
[2, 0],
|
|
91
|
-
],
|
|
92
|
-
];
|
|
93
|
-
const runFeet = [
|
|
94
|
-
[
|
|
95
|
-
[3, 1],
|
|
96
|
-
[-2, 2],
|
|
97
|
-
],
|
|
98
|
-
[
|
|
99
|
-
[0, 0],
|
|
100
|
-
[0, 3],
|
|
101
|
-
],
|
|
102
|
-
[
|
|
103
|
-
[-4, 2],
|
|
104
|
-
[5, 1],
|
|
105
|
-
],
|
|
106
|
-
[
|
|
107
|
-
[-2, 3],
|
|
108
|
-
[2, 0],
|
|
109
|
-
],
|
|
110
|
-
];
|
|
111
|
-
const stride = (d, feet, step, swing, lift = 0) => {
|
|
112
|
-
const [front, back] = feet[step % feet.length] ?? [
|
|
113
|
-
[0, 0],
|
|
114
|
-
[0, 0],
|
|
115
|
-
];
|
|
116
|
-
const sway = step % 2 === 0 ? swing : 0;
|
|
117
|
-
const direction = step % 4 < 2 ? -1 : 1;
|
|
118
|
-
return {
|
|
119
|
-
front,
|
|
120
|
-
back,
|
|
121
|
-
frontHand: [direction * sway, -d.arm + (sway ? 1 : 0)],
|
|
122
|
-
backHand: [-direction * sway, -d.arm + (sway ? 1 : 0)],
|
|
123
|
-
lift: step % 2 === 1 ? lift : 0,
|
|
124
|
-
};
|
|
46
|
+
const icon = (rows, x, y) => (put) => {
|
|
47
|
+
for (const [offset, row] of rows.entries()) {
|
|
48
|
+
for (const [index, key] of [...row].entries()) {
|
|
49
|
+
put(x + index, y - offset, key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
125
52
|
};
|
|
126
|
-
const
|
|
53
|
+
const sparkles = (points) => (put, shape) => {
|
|
127
54
|
for (const [x, y] of points) {
|
|
128
|
-
put(x, y, "y");
|
|
55
|
+
put(x < 0 ? x : shape.width - 1 + x, y + shape.top, "y");
|
|
129
56
|
}
|
|
130
57
|
};
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
for (let
|
|
134
|
-
put(
|
|
135
|
-
}
|
|
136
|
-
for (let y = 0; y < top; y++) {
|
|
137
|
-
put(8, y, "x");
|
|
58
|
+
const laptop = (put, { width }) => {
|
|
59
|
+
span(put, width + 3, width + 8, 0, "x");
|
|
60
|
+
for (let y = 1; y <= 2; y++) {
|
|
61
|
+
put(width + 7, y, "x");
|
|
138
62
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
put(6, y, "l");
|
|
144
|
-
put(7, y, "L");
|
|
63
|
+
span(put, width + 3, width + 7, 3, "L");
|
|
64
|
+
for (let y = 4; y <= 7; y++) {
|
|
65
|
+
put(width + 6, y, "l");
|
|
66
|
+
put(width + 7, y, "L");
|
|
145
67
|
}
|
|
146
68
|
};
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
put(x, y, "Q");
|
|
150
|
-
put(x + 1, y, "Q");
|
|
151
|
-
put(x, y + 1, "o");
|
|
152
|
-
put(x + 1, y + 1, "Q");
|
|
153
|
-
if (steamStep !== undefined) {
|
|
154
|
-
const sway = steamStep % 2;
|
|
155
|
-
put(x + sway, y + 3, "v");
|
|
156
|
-
put(x + 1 - sway, y + 4, "v");
|
|
157
|
-
}
|
|
69
|
+
const held = (rows, dy) => (put, shape) => {
|
|
70
|
+
icon(rows, shape.width + 2, dy(shape))(put, shape, 0);
|
|
158
71
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
]
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
facing: tick === 3 || tick === 4 ? -1 : 1,
|
|
199
|
-
pose: (d) => breathe(d, tick),
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
if (tick < pause + span) {
|
|
203
|
-
const step = tick - pause;
|
|
204
|
-
return {
|
|
205
|
-
x: minimum + step,
|
|
206
|
-
facing: 1,
|
|
207
|
-
pose: (d) => stride(d, walkFeet, step, 1),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
if (tick < 2 * pause + span) {
|
|
211
|
-
const at = tick - pause - span;
|
|
212
|
-
return {
|
|
213
|
-
x: maximum,
|
|
214
|
-
facing: at === 3 || at === 4 ? 1 : -1,
|
|
215
|
-
pose: (d) => breathe(d, at),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
const step = tick - 2 * pause - span;
|
|
72
|
+
// Where the pet stands and what it does on every tick of `counter`.
|
|
73
|
+
function sceneOf(activity, counter, width, shape, mood) {
|
|
74
|
+
const home = 3;
|
|
75
|
+
const minimum = 3;
|
|
76
|
+
const maximum = Math.max(width - shape.width - 3, minimum + 1);
|
|
77
|
+
const travel = maximum - minimum;
|
|
78
|
+
const tick = counter % 4;
|
|
79
|
+
if (activity === "idle" && mood === "sleepy") {
|
|
80
|
+
// Napping in place: breathing, with Zs drifting up.
|
|
81
|
+
return {
|
|
82
|
+
x: Math.floor((width - shape.width) / 2),
|
|
83
|
+
facing: 1,
|
|
84
|
+
pose: { squash: tick < 2 ? 0 : 1, eyes: "closed" },
|
|
85
|
+
props: [
|
|
86
|
+
(put, { width: body, top }) => {
|
|
87
|
+
const rise = counter % 6;
|
|
88
|
+
icon(["zzzz", "..z.", ".z..", "zzzz"], body - 2 + rise, top + 4 + rise)(put, shape, 0);
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (activity === "idle") {
|
|
94
|
+
// Wander: pause, walk across, pause and look about, walk back.
|
|
95
|
+
const pause = 6;
|
|
96
|
+
const cycle = 2 * (pause + travel);
|
|
97
|
+
const at = counter % cycle;
|
|
98
|
+
const isHappy = mood === "happy";
|
|
99
|
+
const walk = (step) => ({
|
|
100
|
+
step: step % 2 === 0 ? 1 : 2,
|
|
101
|
+
look: 1,
|
|
102
|
+
lift: isHappy && step % 2 === 1 ? 1 : 0,
|
|
103
|
+
eyes: isHappy ? "happy" : "open",
|
|
104
|
+
});
|
|
105
|
+
const rest = (moment) => ({
|
|
106
|
+
squash: Math.floor(moment / 2) % 2,
|
|
107
|
+
eyes: isHappy ? "happy" : moment === 4 ? "closed" : "open",
|
|
108
|
+
arms: isHappy && moment % 2 === 0 ? ["up", "up"] : undefined,
|
|
109
|
+
});
|
|
110
|
+
if (at < pause) {
|
|
219
111
|
return {
|
|
220
|
-
x:
|
|
221
|
-
facing: -1,
|
|
222
|
-
pose: (
|
|
112
|
+
x: minimum,
|
|
113
|
+
facing: at === 2 || at === 3 ? -1 : 1,
|
|
114
|
+
pose: rest(at),
|
|
223
115
|
};
|
|
224
116
|
}
|
|
117
|
+
if (at < pause + travel) {
|
|
118
|
+
return { x: minimum + at - pause, facing: 1, pose: walk(at - pause) };
|
|
119
|
+
}
|
|
120
|
+
if (at < 2 * pause + travel) {
|
|
121
|
+
const moment = at - pause - travel;
|
|
122
|
+
return {
|
|
123
|
+
x: maximum,
|
|
124
|
+
facing: moment === 2 || moment === 3 ? 1 : -1,
|
|
125
|
+
pose: rest(moment),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const step = at - 2 * pause - travel;
|
|
129
|
+
return { x: maximum - step, facing: -1, pose: walk(step) };
|
|
130
|
+
}
|
|
131
|
+
switch (activity) {
|
|
225
132
|
case "run": {
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
const step = forward ? tick : tick - lap;
|
|
133
|
+
const lap = Math.max(Math.ceil(travel / 2), 1);
|
|
134
|
+
const at = counter % (2 * lap);
|
|
135
|
+
const forward = at < lap;
|
|
136
|
+
const step = forward ? at : at - lap;
|
|
231
137
|
return {
|
|
232
138
|
x: forward
|
|
233
139
|
? Math.min(minimum + step * 2, maximum)
|
|
234
140
|
: Math.max(maximum - step * 2, minimum),
|
|
235
141
|
facing: forward ? 1 : -1,
|
|
236
|
-
pose:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: [
|
|
244
|
-
[-7, 7],
|
|
245
|
-
[-6, 10],
|
|
246
|
-
]),
|
|
247
|
-
],
|
|
142
|
+
pose: {
|
|
143
|
+
step: counter % 2 === 0 ? 1 : 2,
|
|
144
|
+
lift: counter % 2,
|
|
145
|
+
look: 1,
|
|
146
|
+
arms: counter % 2 === 0 ? ["up", "down"] : ["down", "up"],
|
|
147
|
+
},
|
|
148
|
+
props: [icon(["vv.", "...", ".vv"], -6, 6)],
|
|
248
149
|
};
|
|
249
150
|
}
|
|
250
151
|
case "focus": {
|
|
251
|
-
const step = counter % 4;
|
|
252
152
|
return {
|
|
253
153
|
x: home,
|
|
254
154
|
facing: 1,
|
|
255
|
-
pose:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}),
|
|
155
|
+
pose: {
|
|
156
|
+
look: 1,
|
|
157
|
+
squash: tick < 2 ? 0 : 1,
|
|
158
|
+
arms: ["down", tick % 2 === 0 ? "forward" : "mid"],
|
|
159
|
+
},
|
|
261
160
|
props: [
|
|
262
|
-
|
|
263
|
-
(put,
|
|
264
|
-
|
|
265
|
-
put(
|
|
266
|
-
put(8 - (step % 2), top + ((step + 2) % 4), "y");
|
|
161
|
+
laptop,
|
|
162
|
+
(put, { width: body }) => {
|
|
163
|
+
put(body + 5 + (tick % 2), 9 + tick, "y");
|
|
164
|
+
put(body + 7 - (tick % 2), 9 + ((tick + 2) % 4), "y");
|
|
267
165
|
},
|
|
268
166
|
],
|
|
269
167
|
};
|
|
@@ -271,86 +169,94 @@ function sceneOf(activity, counter, width) {
|
|
|
271
169
|
case "break": {
|
|
272
170
|
const step = counter % 6;
|
|
273
171
|
const sipping = step >= 4;
|
|
274
|
-
const hand = (d) => sipping ? [2, d.neck + 1 - d.shoulder] : [2, -d.arm + 2];
|
|
275
172
|
return {
|
|
276
173
|
x: home,
|
|
277
174
|
facing: 1,
|
|
278
|
-
pose:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
175
|
+
pose: {
|
|
176
|
+
look: 1,
|
|
177
|
+
squash: step % 2,
|
|
178
|
+
eyes: sipping ? "closed" : "open",
|
|
179
|
+
arms: ["down", sipping ? "up" : "mid"],
|
|
180
|
+
},
|
|
283
181
|
props: [
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
182
|
+
held(["QQ", "Qc"], (shape) => sipping ? shape.top - 1 : Math.ceil(shape.height / 2) + 2),
|
|
183
|
+
...(sipping
|
|
184
|
+
? []
|
|
185
|
+
: [
|
|
186
|
+
held(step % 2 === 0 ? [".v", "v."] : ["v.", ".v"], (shape) => Math.ceil(shape.height / 2) + 5),
|
|
187
|
+
]),
|
|
288
188
|
],
|
|
289
189
|
};
|
|
290
190
|
}
|
|
291
191
|
case "eat": {
|
|
292
|
-
const
|
|
293
|
-
const atMouth = step === 1 || step === 2;
|
|
294
|
-
const hand = (d) => atMouth ? [2, d.neck + 1 - d.shoulder] : [2, -d.arm + 2];
|
|
192
|
+
const atMouth = tick === 1 || tick === 2;
|
|
295
193
|
return {
|
|
296
194
|
x: home,
|
|
297
195
|
facing: 1,
|
|
298
|
-
pose:
|
|
196
|
+
pose: {
|
|
197
|
+
look: 1,
|
|
198
|
+
squash: tick === 2 ? 1 : 0,
|
|
199
|
+
eyes: tick === 2 ? "happy" : "open",
|
|
200
|
+
arms: ["down", atMouth ? "up" : "mid"],
|
|
201
|
+
},
|
|
299
202
|
props: [
|
|
300
|
-
|
|
301
|
-
const [x, y] = handAt(d, hand(d));
|
|
302
|
-
return [x + 1, y];
|
|
303
|
-
}, step === 3),
|
|
203
|
+
held(tick === 3 ? [".G", "F.", "FF"] : [".G", "FF", "FF"], (shape) => atMouth ? shape.top - 2 : Math.ceil(shape.height / 2) + 2),
|
|
304
204
|
],
|
|
305
205
|
};
|
|
306
206
|
}
|
|
307
207
|
case "lift": {
|
|
308
|
-
const
|
|
309
|
-
const hand = (d) => step === 2 ? [1, d.arm] : [2, step === 0 ? -d.arm + 3 : 1];
|
|
208
|
+
const up = tick === 1 || tick === 2;
|
|
310
209
|
return {
|
|
311
210
|
x: home,
|
|
312
211
|
facing: 1,
|
|
313
|
-
pose:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
props: [
|
|
212
|
+
pose: {
|
|
213
|
+
arms: up ? ["up", "up"] : ["mid", "mid"],
|
|
214
|
+
lift: tick === 2 ? 1 : 0,
|
|
215
|
+
squash: up ? 0 : 1,
|
|
216
|
+
},
|
|
217
|
+
props: [
|
|
218
|
+
(put, shape) => {
|
|
219
|
+
const y = up ? shape.top + 3 : Math.ceil(shape.height / 2) + 4;
|
|
220
|
+
span(put, -3, shape.width + 2, y, "d");
|
|
221
|
+
icon(["DD", "DD", "DD"], -4, y + 1)(put, shape, 0);
|
|
222
|
+
icon(["DD", "DD", "DD"], shape.width + 2, y + 1)(put, shape, 0);
|
|
223
|
+
},
|
|
224
|
+
],
|
|
319
225
|
};
|
|
320
226
|
}
|
|
321
227
|
case "stretch": {
|
|
322
|
-
const step = counter % 4;
|
|
323
|
-
const up = step < 2;
|
|
324
228
|
return {
|
|
325
229
|
x: home,
|
|
326
230
|
facing: 1,
|
|
327
|
-
pose:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
231
|
+
pose: {
|
|
232
|
+
arms: tick < 2 ? ["up", "up"] : ["mid", "mid"],
|
|
233
|
+
squash: tick < 2 ? -1 : 1,
|
|
234
|
+
eyes: tick === 1 ? "closed" : "open",
|
|
235
|
+
},
|
|
332
236
|
};
|
|
333
237
|
}
|
|
334
238
|
case "levelUp": {
|
|
335
|
-
const step = counter % 4;
|
|
336
|
-
const lift = [0, 2, 3, 1][step] ?? 0;
|
|
337
239
|
return {
|
|
338
240
|
x: home,
|
|
339
241
|
facing: 1,
|
|
340
|
-
pose:
|
|
242
|
+
pose: {
|
|
243
|
+
arms: ["up", "up"],
|
|
244
|
+
lift: [0, 2, 3, 1][tick] ?? 0,
|
|
245
|
+
eyes: "happy",
|
|
246
|
+
},
|
|
341
247
|
props: [
|
|
342
|
-
|
|
248
|
+
sparkles(tick % 2 === 0
|
|
343
249
|
? [
|
|
344
|
-
[-
|
|
345
|
-
[
|
|
346
|
-
[-
|
|
347
|
-
[
|
|
250
|
+
[-3, 2],
|
|
251
|
+
[3, 4],
|
|
252
|
+
[-4, -3],
|
|
253
|
+
[4, -1],
|
|
348
254
|
]
|
|
349
255
|
: [
|
|
350
|
-
[-
|
|
351
|
-
[
|
|
352
|
-
[-
|
|
353
|
-
[
|
|
256
|
+
[-4, 4],
|
|
257
|
+
[4, 2],
|
|
258
|
+
[-3, -1],
|
|
259
|
+
[3, -3],
|
|
354
260
|
]),
|
|
355
261
|
],
|
|
356
262
|
};
|
|
@@ -358,7 +264,7 @@ function sceneOf(activity, counter, width) {
|
|
|
358
264
|
}
|
|
359
265
|
}
|
|
360
266
|
export const intervalOf = (activity) => ({
|
|
361
|
-
idle:
|
|
267
|
+
idle: 220,
|
|
362
268
|
focus: 180,
|
|
363
269
|
break: 380,
|
|
364
270
|
eat: 300,
|
|
@@ -369,206 +275,220 @@ export const intervalOf = (activity) => ({
|
|
|
369
275
|
})[activity];
|
|
370
276
|
// How many ticks one loop of an activity takes on a stage of `width`.
|
|
371
277
|
export function cycleOf(activity, width) {
|
|
372
|
-
const
|
|
278
|
+
const travel = Math.max(width - 21, 4);
|
|
373
279
|
if (activity === "idle") {
|
|
374
|
-
return 2 * (
|
|
280
|
+
return 2 * (6 + travel);
|
|
375
281
|
}
|
|
376
282
|
if (activity === "run") {
|
|
377
|
-
return 2 * Math.max(Math.ceil(
|
|
283
|
+
return 2 * Math.max(Math.ceil(travel / 2), 1);
|
|
378
284
|
}
|
|
379
285
|
return activity === "break" ? 6 : 4;
|
|
380
286
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
shoulder[0] + hand[0],
|
|
389
|
-
shoulder[1] + hand[1],
|
|
390
|
-
]);
|
|
391
|
-
const sleeve = gear.top === 0 ? "U" : "c";
|
|
392
|
-
const skin = isFront ? "s" : "q";
|
|
393
|
-
for (const [index, [x, y]] of points.entries()) {
|
|
394
|
-
const isHand = index === points.length - 1;
|
|
395
|
-
const isBare = gear.top === 0 && index >= 2;
|
|
396
|
-
upper(x, y, isHand || isBare ? skin : sleeve);
|
|
287
|
+
// Behind the body: a cape that flares and ripples, or sparkles of the aura.
|
|
288
|
+
function paintBehind({ put, shape, gear, counter }) {
|
|
289
|
+
if (gear.wear === 2) {
|
|
290
|
+
for (let y = 2; y <= shape.top - 2; y++) {
|
|
291
|
+
const flare = Math.floor((shape.top - y) / 3) + ((counter + y) % 4 < 2 ? 0 : 1);
|
|
292
|
+
span(put, -1 - flare, -1, y, y === 2 ? "s" : "S");
|
|
293
|
+
}
|
|
397
294
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
295
|
+
if (gear.aura === 2) {
|
|
296
|
+
const sway = counter % 2;
|
|
297
|
+
put(-3 - sway, shape.top - 1, "y");
|
|
298
|
+
put(shape.width + 2 + sway, shape.top - 3, "y");
|
|
299
|
+
put(-2, 3 + sway, "y");
|
|
401
300
|
}
|
|
402
301
|
}
|
|
403
|
-
//
|
|
404
|
-
function
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
302
|
+
// 4 short legs, like the Claude Code mascot; sneakers for an endurance goal.
|
|
303
|
+
function paintLegs({ put, shape, look, pose }) {
|
|
304
|
+
const columns = [1, 3, shape.width - 4, shape.width - 2];
|
|
305
|
+
const foot = look.goal === "endurance" ? "w" : "o";
|
|
306
|
+
for (const [index, x] of columns.entries()) {
|
|
307
|
+
const isLifted = (pose.step === 1 && index % 2 === 0) ||
|
|
308
|
+
(pose.step === 2 && index % 2 === 1);
|
|
309
|
+
if (!isLifted) {
|
|
310
|
+
put(x, 0, foot);
|
|
410
311
|
}
|
|
312
|
+
put(x, 1, "o");
|
|
411
313
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
314
|
+
}
|
|
315
|
+
function paintBody({ put, shape, look, gear }) {
|
|
316
|
+
const { width, top } = shape;
|
|
317
|
+
for (let y = 2; y <= top; y++) {
|
|
318
|
+
const isEdge = y === 2 || y === top;
|
|
319
|
+
span(put, isEdge ? 1 : 0, isEdge ? width - 2 : width - 1, y, y === 2 ? "o" : "O");
|
|
320
|
+
}
|
|
321
|
+
span(put, 1, 2, top - 1, "h");
|
|
322
|
+
if (look.goal === "loseFat") {
|
|
323
|
+
span(put, 0, width - 1, top - 1, "w");
|
|
324
|
+
}
|
|
325
|
+
else if (look.goal === "maintain") {
|
|
326
|
+
const middle = Math.floor(width / 2);
|
|
327
|
+
icon(["r.r", "rrr", ".r."], middle - 1, 6)(put, shape, 0);
|
|
328
|
+
}
|
|
329
|
+
if (gear.wear === 1) {
|
|
330
|
+
span(put, 0, width - 1, top - 5, "S");
|
|
331
|
+
put(-1, top - 5, "S");
|
|
332
|
+
put(-2, top - 6, "s");
|
|
417
333
|
}
|
|
418
334
|
}
|
|
419
|
-
function
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
335
|
+
function paintFace({ put, shape, pose, counter }) {
|
|
336
|
+
const look = pose.look ?? 0;
|
|
337
|
+
const eyeY = shape.top - 3;
|
|
338
|
+
const eyes = [
|
|
339
|
+
Math.round(shape.width * 0.3) + look,
|
|
340
|
+
Math.round(shape.width * 0.7) - 1 + look,
|
|
341
|
+
];
|
|
342
|
+
for (const x of eyes) {
|
|
343
|
+
if (pose.eyes === "closed") {
|
|
344
|
+
span(put, x, x + 1, eyeY, "e");
|
|
424
345
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const narrowed = (body.gender === "female" && row >= d.torso - 2) ||
|
|
435
|
-
(body.gender === "male" && row < 2);
|
|
436
|
-
const left = d.left + (narrowed ? 1 : 0);
|
|
437
|
-
upper(left, d.legs + 1 + row, shade);
|
|
438
|
-
span(upper, left + 1, d.right, d.legs + 1 + row, coat);
|
|
439
|
-
}
|
|
440
|
-
if (gear.top === 1) {
|
|
441
|
-
span(upper, d.left, d.left + 1, d.shoulder + 1, shade);
|
|
442
|
-
}
|
|
443
|
-
else if (gear.top === 2) {
|
|
444
|
-
for (let row = 0; row < d.torso; row++) {
|
|
445
|
-
upper(d.right, d.legs + 1 + row, "S");
|
|
346
|
+
else if (pose.eyes === "happy") {
|
|
347
|
+
put(x - 1, eyeY, "e");
|
|
348
|
+
put(x, eyeY + 1, "e");
|
|
349
|
+
put(x + 1, eyeY, "e");
|
|
350
|
+
put(x, eyeY - 2, "r");
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
put(x, eyeY, "e");
|
|
354
|
+
put(x, eyeY + 1, counter % 23 === 0 ? "O" : "e");
|
|
446
355
|
}
|
|
447
|
-
upper(d.right - 1, d.shoulder, "S");
|
|
448
356
|
}
|
|
449
357
|
}
|
|
450
|
-
//
|
|
451
|
-
function
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
upper(-3, y, "h");
|
|
358
|
+
// Side nubs; a muscle goal makes them bigger.
|
|
359
|
+
function paintArms({ put, shape, look, pose }) {
|
|
360
|
+
const size = look.goal === "buildMuscle" ? 3 : 2;
|
|
361
|
+
const [back, front] = pose.arms ?? ["down", "down"];
|
|
362
|
+
const rowOf = (arm) => ({
|
|
363
|
+
down: 3 + size - 1,
|
|
364
|
+
mid: Math.ceil(shape.height / 2) + size,
|
|
365
|
+
up: shape.top + size - 1,
|
|
366
|
+
forward: Math.ceil(shape.height / 2) + size,
|
|
367
|
+
})[arm];
|
|
368
|
+
const nub = (x, arm) => {
|
|
369
|
+
const reach = arm === "forward" ? 1 : 0;
|
|
370
|
+
for (let row = 0; row < size; row++) {
|
|
371
|
+
span(put, x + reach, x + reach + size - 1, rowOf(arm) - row, row === size - 1 ? "o" : "O");
|
|
465
372
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
373
|
+
};
|
|
374
|
+
nub(-size, back);
|
|
375
|
+
nub(shape.width, front);
|
|
376
|
+
}
|
|
377
|
+
// Gender sets an ornament at the back of the head, level the gear on top.
|
|
378
|
+
function paintHead({ put, shape, look, gear, counter }) {
|
|
379
|
+
const { width, top } = shape;
|
|
380
|
+
if (look.gender === "female") {
|
|
381
|
+
icon(["r.r", "rrr", "r.r"], 0, top + 3)(put, shape, 0);
|
|
469
382
|
}
|
|
470
|
-
else if (
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
383
|
+
else if (look.gender === "male") {
|
|
384
|
+
put(1, top + 1, "O");
|
|
385
|
+
put(2, top + 2, "O");
|
|
386
|
+
put(3, top + 1, "O");
|
|
474
387
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
388
|
+
else {
|
|
389
|
+
put(2, top + 1, "G");
|
|
390
|
+
put(1, top + 2, "G");
|
|
391
|
+
put(3, top + 3, "G");
|
|
392
|
+
}
|
|
393
|
+
if (gear.gadget >= 1) {
|
|
394
|
+
span(put, 5, width - 2, top + 1, "k");
|
|
395
|
+
icon(["S", "S"], -1, top - 1)(put, shape, 0);
|
|
396
|
+
icon(["S", "S"], width, top - 1)(put, shape, 0);
|
|
397
|
+
}
|
|
398
|
+
if (gear.gadget === 2) {
|
|
399
|
+
put(width - 3, top + 2, "k");
|
|
400
|
+
put(width - 3, top + 3, counter % 2 === 0 ? "t" : "y");
|
|
487
401
|
}
|
|
488
402
|
if (gear.headwear === 1) {
|
|
489
|
-
span(
|
|
490
|
-
span(
|
|
491
|
-
span(
|
|
403
|
+
span(put, 5, width - 3, top + 1, "S");
|
|
404
|
+
span(put, 6, width - 4, top + 2, "S");
|
|
405
|
+
span(put, width - 2, width, top + 1, "s");
|
|
492
406
|
}
|
|
493
407
|
else if (gear.headwear === 2) {
|
|
494
|
-
span(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
408
|
+
span(put, 6, width - 3, top + 1, "g");
|
|
409
|
+
put(6, top + 2, "g");
|
|
410
|
+
put(Math.floor((width + 3) / 2), top + 2, "g");
|
|
411
|
+
put(width - 3, top + 2, "g");
|
|
412
|
+
put(Math.floor((width + 3) / 2), top + 1, "t");
|
|
413
|
+
}
|
|
414
|
+
if (gear.aura >= 1) {
|
|
415
|
+
const bob = counter % 4 < 2 ? 0 : 1;
|
|
416
|
+
put(width + 4, top + 2 + bob, "t");
|
|
417
|
+
put(width + 4, top + 3 + bob, "y");
|
|
498
418
|
}
|
|
499
|
-
}
|
|
500
|
-
function drawCharacter(paint) {
|
|
501
|
-
const { d, pose } = paint;
|
|
502
|
-
paintBack(paint);
|
|
503
|
-
paintArm(paint, [d.left + 1, d.shoulder], pose.backHand, false);
|
|
504
|
-
paintLegs(paint);
|
|
505
|
-
paintTorso(paint);
|
|
506
|
-
paintHead(paint);
|
|
507
|
-
paintHeadgear(paint);
|
|
508
|
-
paintArm(paint, shoulderOf(d), pose.frontHand, true);
|
|
509
419
|
}
|
|
510
420
|
export function paletteOf(palette) {
|
|
511
|
-
const { cloak, cloakShade,
|
|
421
|
+
const { cloak, cloakShade, glow } = palette.sprite;
|
|
512
422
|
return new Map([
|
|
513
|
-
["
|
|
514
|
-
["b", "#5A4632"],
|
|
515
|
-
["C", cloak],
|
|
516
|
-
["c", cloakShade],
|
|
423
|
+
["c", "#5A3A25"],
|
|
517
424
|
["D", "#50555F"],
|
|
518
425
|
["d", "#8A8F99"],
|
|
519
|
-
["e", "#
|
|
426
|
+
["e", "#1E1E1E"],
|
|
520
427
|
["F", "#C8473F"],
|
|
521
|
-
["G", "#
|
|
428
|
+
["G", "#6FA35A"],
|
|
522
429
|
["g", "#D9B45A"],
|
|
523
|
-
["h",
|
|
430
|
+
["h", palette.soft],
|
|
524
431
|
["k", "#2B2B2B"],
|
|
525
432
|
["L", "#4A505B"],
|
|
526
433
|
["l", glow],
|
|
527
|
-
["
|
|
528
|
-
["
|
|
529
|
-
["p", "#4F8189"],
|
|
434
|
+
["O", palette.accent],
|
|
435
|
+
["o", palette.deep],
|
|
530
436
|
["Q", "#E6E0D4"],
|
|
531
|
-
["
|
|
532
|
-
["S",
|
|
533
|
-
["s",
|
|
534
|
-
["T", "#
|
|
437
|
+
["r", "#E87A90"],
|
|
438
|
+
["S", cloak],
|
|
439
|
+
["s", cloakShade],
|
|
440
|
+
["T", "#E5533D"],
|
|
535
441
|
["t", glow],
|
|
536
|
-
["U", "#B4AE9F"],
|
|
537
442
|
["v", "#9AA0A8"],
|
|
538
|
-
["W", "#C8C8C8"],
|
|
539
443
|
["w", "#F2F2F2"],
|
|
540
444
|
["x", "#3D414B"],
|
|
541
445
|
["y", palette.soft],
|
|
542
|
-
["z",
|
|
446
|
+
["z", "#C8C8C8"],
|
|
543
447
|
]);
|
|
544
448
|
}
|
|
545
449
|
// The pixel keys of 1 frame, row by row, "." where nothing is drawn.
|
|
546
|
-
export function composeScene({ activity, counter, level,
|
|
450
|
+
export function composeScene({ activity, counter, level, look, mood, badges, width, }) {
|
|
547
451
|
const canvas = Array.from({ length: stageHeight }, () => Array.from({ length: width }, () => "."));
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
const pose = scene.pose(d);
|
|
551
|
-
const lift = pose.lift ?? 0;
|
|
552
|
-
const put = (x, y, key) => {
|
|
553
|
-
const column = scene.x + scene.facing * x;
|
|
554
|
-
const row = canvas[stageHeight - 1 - (y + lift)];
|
|
452
|
+
const plain = (column, y, key) => {
|
|
453
|
+
const row = canvas[stageHeight - 1 - y];
|
|
555
454
|
if (row && column >= 0 && column < width && key !== ".") {
|
|
556
455
|
row[column] = key;
|
|
557
456
|
}
|
|
558
457
|
};
|
|
559
|
-
|
|
458
|
+
const baseShape = shapeOf(look, level);
|
|
459
|
+
const scene = sceneOf(activity, counter, width, baseShape, mood);
|
|
460
|
+
const shape = shapeOf(look, level, scene.pose.squash ?? 0);
|
|
461
|
+
const lift = scene.pose.lift ?? 0;
|
|
462
|
+
// Local x runs from the body's left edge toward where the pet faces.
|
|
463
|
+
const put = (x, y, key) => {
|
|
464
|
+
const column = scene.facing === 1 ? scene.x + x : scene.x + baseShape.width - 1 - x;
|
|
465
|
+
plain(column, y + lift, key);
|
|
466
|
+
};
|
|
467
|
+
const paint = {
|
|
560
468
|
put,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
},
|
|
564
|
-
d,
|
|
565
|
-
body,
|
|
469
|
+
shape,
|
|
470
|
+
look,
|
|
566
471
|
gear: gearOf(level),
|
|
567
|
-
pose,
|
|
472
|
+
pose: scene.pose,
|
|
568
473
|
counter,
|
|
569
|
-
}
|
|
474
|
+
};
|
|
475
|
+
paintBehind(paint);
|
|
476
|
+
paintLegs(paint);
|
|
477
|
+
paintBody(paint);
|
|
478
|
+
paintFace(paint);
|
|
479
|
+
paintArms(paint);
|
|
480
|
+
paintHead(paint);
|
|
570
481
|
for (const draw of scene.props ?? []) {
|
|
571
|
-
draw(put,
|
|
482
|
+
draw(put, shape, counter);
|
|
483
|
+
}
|
|
484
|
+
// Today's badges float in the top right corner, whatever the pet does.
|
|
485
|
+
const icons = [
|
|
486
|
+
badges.focus ? ["GGG", "TTT", "TTT"] : undefined,
|
|
487
|
+
badges.meal ? [".G.", "FFF", ".F."] : undefined,
|
|
488
|
+
badges.workout ? ["D.D", "DdD", "D.D"] : undefined,
|
|
489
|
+
].filter((rows) => rows !== undefined);
|
|
490
|
+
for (const [index, rows] of icons.entries()) {
|
|
491
|
+
icon(rows, width - 4 - index * 4, stageHeight - 2)(plain, shape, 0);
|
|
572
492
|
}
|
|
573
493
|
return canvas.map((row) => row.join(""));
|
|
574
494
|
}
|