honeytree 1.1.0 → 1.1.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/package.json +1 -1
- package/src/renderers/terminal.js +84 -29
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
|
|
3
3
|
import { getDynamicScene, getCrystalPalette } from "../core/animation.js";
|
|
4
|
-
import { getEnvironmentSnapshot } from "../core/environment.js";
|
|
4
|
+
import { getEnvironmentSnapshot, lerpColor } from "../core/environment.js";
|
|
5
5
|
import { VIRTUAL_WIDTH } from "../core/progression.js";
|
|
6
6
|
import {
|
|
7
7
|
getAnimalSprite,
|
|
@@ -20,14 +20,20 @@ export const SCENE_HEIGHT = ART_ROWS + STATS_ROWS;
|
|
|
20
20
|
|
|
21
21
|
function createBuffer(width) {
|
|
22
22
|
return Array.from({ length: ART_ROWS }, () =>
|
|
23
|
-
Array.from({ length: width }, () => ({ char: " ", color: null })),
|
|
23
|
+
Array.from({ length: width }, () => ({ char: " ", color: null, bg: null })),
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function paint(buffer, x, y, char, color) {
|
|
28
28
|
if (y < 0 || y >= buffer.length) return;
|
|
29
29
|
if (x < 0 || x >= buffer[0].length) return;
|
|
30
|
-
buffer[y][x] = { char, color };
|
|
30
|
+
buffer[y][x] = { char, color, bg: buffer[y][x].bg };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function paintBg(buffer, x, y, bg) {
|
|
34
|
+
if (y < 0 || y >= buffer.length) return;
|
|
35
|
+
if (x < 0 || x >= buffer[0].length) return;
|
|
36
|
+
buffer[y][x].bg = bg;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
function compositeSprite(buffer, sprite, centerX, baseY, palette) {
|
|
@@ -44,11 +50,42 @@ function compositeSprite(buffer, sprite, centerX, baseY, palette) {
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
function colorize(text, color, enabled) {
|
|
48
|
-
if (!enabled
|
|
49
|
-
|
|
53
|
+
function colorize(text, color, bg, enabled) {
|
|
54
|
+
if (!enabled) return text;
|
|
55
|
+
if (!color && !bg) return text;
|
|
56
|
+
let fn = chalk;
|
|
57
|
+
if (bg) fn = fn.bgHex(bg);
|
|
58
|
+
if (color) fn = fn.hex(color);
|
|
59
|
+
return fn(text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function drawSkyBackground(buffer, width, environment) {
|
|
63
|
+
const sky = environment.sky;
|
|
64
|
+
// Sky rows: vertical gradient from top → mid → bottom
|
|
65
|
+
for (let y = 0; y < SKY_ROWS; y++) {
|
|
66
|
+
const t = y / Math.max(1, SKY_ROWS - 1);
|
|
67
|
+
let color;
|
|
68
|
+
if (t < 0.5) {
|
|
69
|
+
color = lerpColor(sky.top, sky.mid, t * 2);
|
|
70
|
+
} else {
|
|
71
|
+
color = lerpColor(sky.mid, sky.bottom, (t - 0.5) * 2);
|
|
72
|
+
}
|
|
73
|
+
for (let x = 0; x < width; x++) {
|
|
74
|
+
paintBg(buffer, x, y, color);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Forest rows: blend from sky.bottom toward ground
|
|
78
|
+
for (let y = SKY_ROWS; y < SKY_ROWS + FOREST_ROWS; y++) {
|
|
79
|
+
const t = (y - SKY_ROWS) / Math.max(1, FOREST_ROWS - 1);
|
|
80
|
+
const color = lerpColor(sky.bottom, environment.palette.groundDark, t * 0.7);
|
|
81
|
+
for (let x = 0; x < width; x++) {
|
|
82
|
+
paintBg(buffer, x, y, color);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Ground row: solid ground color
|
|
86
|
+
for (let x = 0; x < width; x++) {
|
|
87
|
+
paintBg(buffer, x, ART_ROWS - 1, environment.palette.groundDark);
|
|
50
88
|
}
|
|
51
|
-
return chalk.hex(color)(text);
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
function drawSky(buffer, width, environment, tick) {
|
|
@@ -126,6 +163,20 @@ function scaleX(virtualX, width) {
|
|
|
126
163
|
return Math.round((virtualX / VIRTUAL_WIDTH) * width);
|
|
127
164
|
}
|
|
128
165
|
|
|
166
|
+
function spaceTrees(trees, width, minGap) {
|
|
167
|
+
const sorted = [...trees].sort((a, b) => a.x_position - b.x_position);
|
|
168
|
+
const result = [];
|
|
169
|
+
let lastScreenX = -Infinity;
|
|
170
|
+
for (const tree of sorted) {
|
|
171
|
+
const screenX = Math.round((tree.x_position / VIRTUAL_WIDTH) * width);
|
|
172
|
+
if (screenX - lastScreenX >= minGap) {
|
|
173
|
+
result.push(tree);
|
|
174
|
+
lastScreenX = screenX;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
129
180
|
function drawForest(buffer, width, state, environment, tick) {
|
|
130
181
|
const treeBaseY = SKY_ROWS + FOREST_ROWS - 1;
|
|
131
182
|
const isSunsetSilhouette = environment.sky.name === "sunset";
|
|
@@ -138,23 +189,24 @@ function drawForest(buffer, width, state, environment, tick) {
|
|
|
138
189
|
|
|
139
190
|
const crystalPalette = getCrystalPalette(tick);
|
|
140
191
|
|
|
141
|
-
//
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
192
|
+
// Separate trees by row, then filter for spacing
|
|
193
|
+
const minGap = { back: 6, mid: 10, front: 14 };
|
|
194
|
+
const rows = ["back", "mid", "front"];
|
|
195
|
+
for (const targetRow of rows) {
|
|
196
|
+
const rowTrees = state.trees.filter((t) => (t.row || "front") === targetRow);
|
|
197
|
+
const spaced = spaceTrees(rowTrees, width, minGap[targetRow]);
|
|
198
|
+
const rowY = targetRow === "back" ? treeBaseY - 2 : targetRow === "mid" ? treeBaseY - 1 : treeBaseY;
|
|
146
199
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
200
|
+
for (const tree of spaced) {
|
|
201
|
+
let palette = environment.palette;
|
|
202
|
+
if (tree.species === "crystal_tree") {
|
|
203
|
+
palette = { ...environment.palette, ...crystalPalette };
|
|
204
|
+
}
|
|
205
|
+
if (isSunsetSilhouette) {
|
|
206
|
+
palette = silhouettePalette;
|
|
207
|
+
}
|
|
208
|
+
compositeSprite(buffer, getTreeSprite(tree.species), scaleX(tree.x_position, width), rowY, palette);
|
|
156
209
|
}
|
|
157
|
-
compositeSprite(buffer, getTreeSprite(tree.species), scaleX(tree.x_position, width), rowY, palette);
|
|
158
210
|
}
|
|
159
211
|
|
|
160
212
|
for (const element of state.ground_elements) {
|
|
@@ -162,7 +214,9 @@ function drawForest(buffer, width, state, environment, tick) {
|
|
|
162
214
|
}
|
|
163
215
|
|
|
164
216
|
const scene = getDynamicScene(state, width, tick, environment);
|
|
165
|
-
|
|
217
|
+
const maxAnimals = Math.max(2, Math.min(4, Math.floor(width / 25)));
|
|
218
|
+
const visibleAnimals = scene.animals.slice(0, maxAnimals);
|
|
219
|
+
for (const animal of visibleAnimals) {
|
|
166
220
|
if (animal.type === "owl" && environment.sky.name !== "night") {
|
|
167
221
|
continue;
|
|
168
222
|
}
|
|
@@ -187,12 +241,12 @@ function buildStatsLine(state, environment, width, color) {
|
|
|
187
241
|
const animals = `${state.animals.length} animal${state.animals.length === 1 ? "" : "s"}`;
|
|
188
242
|
const streak = `${state.current_streak} day streak`;
|
|
189
243
|
const weather = `${environment.season.icon} ${environment.season.label} ${environment.weather.label}`;
|
|
190
|
-
const separator = colorize(" • ", "#6b7280", color);
|
|
244
|
+
const separator = colorize(" • ", "#6b7280", null, color);
|
|
191
245
|
const line = [
|
|
192
|
-
colorize("🌲 " + trees, "#7bd389", color),
|
|
193
|
-
colorize("🐇 " + animals, "#f4d35e", color),
|
|
194
|
-
colorize("🔥 " + streak, "#ee964b", color),
|
|
195
|
-
colorize(weather, "#d4d4d8", color),
|
|
246
|
+
colorize("🌲 " + trees, "#7bd389", null, color),
|
|
247
|
+
colorize("🐇 " + animals, "#f4d35e", null, color),
|
|
248
|
+
colorize("🔥 " + streak, "#ee964b", null, color),
|
|
249
|
+
colorize(weather, "#d4d4d8", null, color),
|
|
196
250
|
].join(separator);
|
|
197
251
|
|
|
198
252
|
if (line.length >= width) {
|
|
@@ -207,6 +261,7 @@ export function buildTerminalScene(state, termWidth = 80, tick = 0, options = {}
|
|
|
207
261
|
const environment = getEnvironmentSnapshot(date, state.current_streak ?? 0);
|
|
208
262
|
const buffer = createBuffer(width);
|
|
209
263
|
|
|
264
|
+
drawSkyBackground(buffer, width, environment);
|
|
210
265
|
drawSky(buffer, width, environment, tick);
|
|
211
266
|
drawWeather(buffer, width, environment, tick);
|
|
212
267
|
drawSeasonParticles(buffer, width, environment, tick);
|
|
@@ -233,7 +288,7 @@ export function renderTerminalFrame(state, termWidth = 80, tick = 0, options = {
|
|
|
233
288
|
const scene = buildTerminalScene(state, termWidth, tick, options);
|
|
234
289
|
const lines = scene.buffer.map((row) =>
|
|
235
290
|
row
|
|
236
|
-
.map((cell) => colorize(cell.char, cell.color, options.color !== false))
|
|
291
|
+
.map((cell) => colorize(cell.char, cell.color, cell.bg, options.color !== false))
|
|
237
292
|
.join(""),
|
|
238
293
|
);
|
|
239
294
|
lines.push(scene.statsLine);
|