@duetso/agent 0.1.139 → 0.1.141
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/dist/package.json +1 -1
- package/dist/src/cli/help.d.ts.map +1 -1
- package/dist/src/cli/help.js +20 -7
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/memory-reflect.d.ts.map +1 -1
- package/dist/src/cli/memory-reflect.js +3 -2
- package/dist/src/cli/memory-reflect.js.map +1 -1
- package/dist/src/memory/observation-groups.d.ts +11 -1
- package/dist/src/memory/observation-groups.d.ts.map +1 -1
- package/dist/src/memory/observation-groups.js +15 -5
- package/dist/src/memory/observation-groups.js.map +1 -1
- package/dist/src/memory/observational-prompts.d.ts +21 -2
- package/dist/src/memory/observational-prompts.d.ts.map +1 -1
- package/dist/src/memory/observational-prompts.js +89 -12
- package/dist/src/memory/observational-prompts.js.map +1 -1
- package/dist/src/memory/observational.d.ts +36 -6
- package/dist/src/memory/observational.d.ts.map +1 -1
- package/dist/src/memory/observational.js +118 -53
- package/dist/src/memory/observational.js.map +1 -1
- package/dist/src/session/session.d.ts +6 -0
- package/dist/src/session/session.d.ts.map +1 -1
- package/dist/src/session/session.js +7 -0
- package/dist/src/session/session.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +19 -0
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/autocomplete-controller.d.ts +7 -0
- package/dist/src/tui/autocomplete-controller.d.ts.map +1 -1
- package/dist/src/tui/autocomplete-controller.js +6 -0
- package/dist/src/tui/autocomplete-controller.js.map +1 -1
- package/dist/src/tui/boot-screen.d.ts.map +1 -1
- package/dist/src/tui/boot-screen.js +2 -10
- package/dist/src/tui/boot-screen.js.map +1 -1
- package/dist/src/tui/controllers.d.ts.map +1 -1
- package/dist/src/tui/controllers.js +24 -0
- package/dist/src/tui/controllers.js.map +1 -1
- package/dist/src/tui/dino/index.d.ts +31 -0
- package/dist/src/tui/dino/index.d.ts.map +1 -0
- package/dist/src/tui/dino/index.js +302 -0
- package/dist/src/tui/dino/index.js.map +1 -0
- package/dist/src/tui/dino/input.d.ts +12 -0
- package/dist/src/tui/dino/input.d.ts.map +1 -0
- package/dist/src/tui/dino/input.js +44 -0
- package/dist/src/tui/dino/input.js.map +1 -0
- package/dist/src/tui/dino/persistence.d.ts +3 -0
- package/dist/src/tui/dino/persistence.d.ts.map +1 -0
- package/dist/src/tui/dino/persistence.js +36 -0
- package/dist/src/tui/dino/persistence.js.map +1 -0
- package/dist/src/tui/dino/render.d.ts +18 -0
- package/dist/src/tui/dino/render.d.ts.map +1 -0
- package/dist/src/tui/dino/render.js +161 -0
- package/dist/src/tui/dino/render.js.map +1 -0
- package/dist/src/tui/dino/state.d.ts +141 -0
- package/dist/src/tui/dino/state.d.ts.map +1 -0
- package/dist/src/tui/dino/state.js +156 -0
- package/dist/src/tui/dino/state.js.map +1 -0
- package/dist/src/tui/dino/tick.d.ts +10 -0
- package/dist/src/tui/dino/tick.d.ts.map +1 -0
- package/dist/src/tui/dino/tick.js +117 -0
- package/dist/src/tui/dino/tick.js.map +1 -0
- package/dist/src/tui/dino/visibility.d.ts +2 -0
- package/dist/src/tui/dino/visibility.d.ts.map +1 -0
- package/dist/src/tui/dino/visibility.js +19 -0
- package/dist/src/tui/dino/visibility.js.map +1 -0
- package/dist/src/tui/key-handlers.d.ts +7 -0
- package/dist/src/tui/key-handlers.d.ts.map +1 -1
- package/dist/src/tui/key-handlers.js +25 -1
- package/dist/src/tui/key-handlers.js.map +1 -1
- package/dist/src/tui/layout.d.ts +7 -0
- package/dist/src/tui/layout.d.ts.map +1 -1
- package/dist/src/tui/layout.js +14 -1
- package/dist/src/tui/layout.js.map +1 -1
- package/dist/src/tui/slash-commands.d.ts +8 -0
- package/dist/src/tui/slash-commands.d.ts.map +1 -1
- package/dist/src/tui/slash-commands.js +17 -0
- package/dist/src/tui/slash-commands.js.map +1 -1
- package/dist/src/tui/status-controller.d.ts +9 -0
- package/dist/src/tui/status-controller.d.ts.map +1 -1
- package/dist/src/tui/status-controller.js +28 -0
- package/dist/src/tui/status-controller.js.map +1 -1
- package/dist/src/turn-runner/shell-state-handle.d.ts +8 -1
- package/dist/src/turn-runner/shell-state-handle.d.ts.map +1 -1
- package/dist/src/turn-runner/shell-state-handle.js +4 -1
- package/dist/src/turn-runner/shell-state-handle.js.map +1 -1
- package/dist/src/turn-runner/skill-context.d.ts +6 -0
- package/dist/src/turn-runner/skill-context.d.ts.map +1 -1
- package/dist/src/turn-runner/skill-context.js +12 -0
- package/dist/src/turn-runner/skill-context.js.map +1 -1
- package/dist/src/turn-runner/state-machine-controller.d.ts +8 -2
- package/dist/src/turn-runner/state-machine-controller.d.ts.map +1 -1
- package/dist/src/turn-runner/state-machine-controller.js +51 -20
- package/dist/src/turn-runner/state-machine-controller.js.map +1 -1
- package/dist/src/turn-runner/tools.js +17 -21
- package/dist/src/turn-runner/tools.js.map +1 -1
- package/dist/src/turn-runner/turn-runner.d.ts +15 -3
- package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
- package/dist/src/turn-runner/turn-runner.js +33 -19
- package/dist/src/turn-runner/turn-runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// OpenTUI panel that hosts the dino game. Owns the tick timer, the
|
|
2
|
+
// `BoxRenderable` mounted into the layout, and the freeze/resume lifecycle
|
|
3
|
+
// that the StatusController drives via its `onRunningChange` hook.
|
|
4
|
+
//
|
|
5
|
+
// Lifecycle contract (matches the spec we agreed in design):
|
|
6
|
+
// - toggle() : user pressed Ctrl-G; expand/collapse the panel.
|
|
7
|
+
// - freeze() : agent flipped to needs-user; snap dino to ground
|
|
8
|
+
// and stop the world. No-op when collapsed.
|
|
9
|
+
// - resume() : agent flipped to busy; start the 3-2-1 countdown
|
|
10
|
+
// followed by a grace gap. No-op when collapsed or
|
|
11
|
+
// when no run is in progress.
|
|
12
|
+
// - handleKey(key) : input router forwards keystrokes here while the
|
|
13
|
+
// panel is expanded and the agent is busy. Returns
|
|
14
|
+
// true when the key was consumed.
|
|
15
|
+
// - destroy() : tear down the tick timer; persist high score.
|
|
16
|
+
import { BoxRenderable, TextRenderable } from "@opentui/core";
|
|
17
|
+
import { SIDEBAR_WIDTH } from "../sidebar.js";
|
|
18
|
+
import { COLORS } from "../theme.js";
|
|
19
|
+
import { actionForKey, applyJump, applyStart } from "./input.js";
|
|
20
|
+
import { loadHighScore, saveHighScore } from "./persistence.js";
|
|
21
|
+
import { EXPANDED_ROWS, renderExpanded } from "./render.js";
|
|
22
|
+
import { panelVisibleRowCount } from "./visibility.js";
|
|
23
|
+
import { beginCountdown, expandResumeKind, freezeRun, initialState, setFieldWidth, } from "./state.js";
|
|
24
|
+
import { tick } from "./tick.js";
|
|
25
|
+
/** Horizontal chrome around the dino panel: the panel sits inside the main
|
|
26
|
+
* layout column, which itself sits to the left of the sidebar. We deduct
|
|
27
|
+
* the sidebar plus a few cells of frame padding to land on a field width
|
|
28
|
+
* that fills the available column without overflowing it. */
|
|
29
|
+
const PANEL_CHROME_COLUMNS = 4;
|
|
30
|
+
/** Cadence of the physics loop. Chosen high enough that a jump arc feels
|
|
31
|
+
* smooth in a terminal but low enough that the redraw cost is negligible
|
|
32
|
+
* (15 fps ≈ 67ms per frame). The countdown layer uses this same value to
|
|
33
|
+
* size the 3-2-1 phase. */
|
|
34
|
+
const TICKS_PER_SECOND = 15;
|
|
35
|
+
const TICK_INTERVAL_MS = Math.round(1000 / TICKS_PER_SECOND);
|
|
36
|
+
export function createDinoPanel(opts) {
|
|
37
|
+
const { renderer } = opts;
|
|
38
|
+
// The panel is collapsed by default and re-collapses every time the
|
|
39
|
+
// agent goes idle. "Hint only" is the starting point for every busy
|
|
40
|
+
// cycle; if the user wants the game they toggle it open each time.
|
|
41
|
+
// We do not persist expanded state across sessions or across busy
|
|
42
|
+
// cycles — the simpler contract is more predictable.
|
|
43
|
+
let expanded = false;
|
|
44
|
+
// When expanded, tracks whether game keys (space, ArrowUp) belong to
|
|
45
|
+
// the dino or to the composer. Defaults to true on every expand so
|
|
46
|
+
// Ctrl-G straight into a playable game; flipping to false via a
|
|
47
|
+
// second Ctrl-G keeps the panel visible but releases the keys back
|
|
48
|
+
// to the textarea so the user can type a follow-up without
|
|
49
|
+
// collapsing the game.
|
|
50
|
+
let gameFocused = true;
|
|
51
|
+
let state = initialState(loadHighScore(), computeFieldWidth(renderer));
|
|
52
|
+
// Tracks whether the most recent freeze was caused by the agent needing
|
|
53
|
+
// input (so resume runs the 3-2-1 + grace gap) vs. a manual collapse
|
|
54
|
+
// (so re-expand resumes instantly per the spec).
|
|
55
|
+
let frozenByAgent = false;
|
|
56
|
+
// Mirrors the StatusController running signal. Ctrl-G is only
|
|
57
|
+
// accepted while this is true; at idle the panel is invisible and
|
|
58
|
+
// reserves no rows.
|
|
59
|
+
let agentBusy = false;
|
|
60
|
+
let ticker;
|
|
61
|
+
// Last persisted high score, so we only write on improvement. Hydrated
|
|
62
|
+
// from disk at construction time.
|
|
63
|
+
let persistedHighScore = state.highScore;
|
|
64
|
+
const view = new BoxRenderable(renderer, {
|
|
65
|
+
flexDirection: "column",
|
|
66
|
+
flexShrink: 0,
|
|
67
|
+
border: false,
|
|
68
|
+
});
|
|
69
|
+
// One TextRenderable per displayed row. The expanded layout reserves
|
|
70
|
+
// EXPANDED_ROWS rows; collapsed reuses the first row and hides the
|
|
71
|
+
// rest. Building a fixed row pool avoids reflowing the box on every
|
|
72
|
+
// frame.
|
|
73
|
+
const rows = Array.from({ length: EXPANDED_ROWS }, () => {
|
|
74
|
+
const row = new TextRenderable(renderer, {
|
|
75
|
+
content: "",
|
|
76
|
+
fg: COLORS.hint,
|
|
77
|
+
height: 1,
|
|
78
|
+
flexShrink: 0,
|
|
79
|
+
selectable: false,
|
|
80
|
+
});
|
|
81
|
+
view.add(row);
|
|
82
|
+
return row;
|
|
83
|
+
});
|
|
84
|
+
syncVisibility();
|
|
85
|
+
paint();
|
|
86
|
+
// Resize handler. The renderer fires "resize" on terminal size changes;
|
|
87
|
+
// we recompute the playfield width and let the next paint stretch the
|
|
88
|
+
// ground line / obstacles to the new column. We keep a reference so the
|
|
89
|
+
// panel's `destroy()` can detach it on teardown.
|
|
90
|
+
const handleResize = () => {
|
|
91
|
+
const next = computeFieldWidth(renderer);
|
|
92
|
+
if (next === state.fieldWidth)
|
|
93
|
+
return;
|
|
94
|
+
state = setFieldWidth(state, next);
|
|
95
|
+
paint();
|
|
96
|
+
};
|
|
97
|
+
renderer.on("resize", handleResize);
|
|
98
|
+
function isExpanded() {
|
|
99
|
+
return expanded;
|
|
100
|
+
}
|
|
101
|
+
function isGameFocused() {
|
|
102
|
+
return expanded && gameFocused;
|
|
103
|
+
}
|
|
104
|
+
function toggle() {
|
|
105
|
+
// The dino panel is a "while-you-wait" affordance: it only exists
|
|
106
|
+
// while the agent is actually busy. At rest there is no hint and no
|
|
107
|
+
// toggle — Ctrl-G is a no-op so a fresh `duet` session is clean.
|
|
108
|
+
if (!agentBusy)
|
|
109
|
+
return;
|
|
110
|
+
// Three-step cycle so the user can toggle keystroke ownership
|
|
111
|
+
// without losing what they typed:
|
|
112
|
+
// collapsed → expanded + game-focused
|
|
113
|
+
// expanded + game → expanded + composer-focused (typing)
|
|
114
|
+
// expanded + composer → collapsed
|
|
115
|
+
if (!expanded) {
|
|
116
|
+
expanded = true;
|
|
117
|
+
gameFocused = true;
|
|
118
|
+
}
|
|
119
|
+
else if (gameFocused) {
|
|
120
|
+
// Stay expanded; just release keys to the composer. The game
|
|
121
|
+
// keeps animating in the background so the user sees obstacles
|
|
122
|
+
// approach while they type.
|
|
123
|
+
gameFocused = false;
|
|
124
|
+
syncVisibility();
|
|
125
|
+
paint();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
expanded = false;
|
|
130
|
+
gameFocused = true;
|
|
131
|
+
}
|
|
132
|
+
if (expanded) {
|
|
133
|
+
// Re-expanding into a frozen run: if the agent caused the freeze
|
|
134
|
+
// (needs-user → answered → busy again, but `resume()` no-op'd
|
|
135
|
+
// because the panel was collapsed), run the 3-2-1 + grace gap the
|
|
136
|
+
// live resume path uses. If the user collapsed manually, resume
|
|
137
|
+
// instantly — they explicitly chose to peek away.
|
|
138
|
+
const kind = expandResumeKind(state.phase, frozenByAgent);
|
|
139
|
+
if (kind === "countdown") {
|
|
140
|
+
state = beginCountdown(state, TICKS_PER_SECOND);
|
|
141
|
+
frozenByAgent = false;
|
|
142
|
+
}
|
|
143
|
+
else if (kind === "run") {
|
|
144
|
+
state = { ...state, phase: { kind: "running" } };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Collapsing while running snaps the world quiet without ending the
|
|
149
|
+
// run; the user can re-expand and pick up where they left off.
|
|
150
|
+
if (state.phase.kind === "running" || state.phase.kind === "grace") {
|
|
151
|
+
state = freezeRun(state);
|
|
152
|
+
frozenByAgent = false;
|
|
153
|
+
}
|
|
154
|
+
stopTicker();
|
|
155
|
+
}
|
|
156
|
+
syncVisibility();
|
|
157
|
+
paint();
|
|
158
|
+
if (expanded && stateNeedsTicking())
|
|
159
|
+
startTicker();
|
|
160
|
+
}
|
|
161
|
+
function resume() {
|
|
162
|
+
if (!expanded)
|
|
163
|
+
return;
|
|
164
|
+
// No run in progress → nothing to resume; the user starts a fresh run
|
|
165
|
+
// themselves by pressing space.
|
|
166
|
+
if (state.phase.kind !== "frozen")
|
|
167
|
+
return;
|
|
168
|
+
if (frozenByAgent) {
|
|
169
|
+
state = beginCountdown(state, TICKS_PER_SECOND);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
state = { ...state, phase: { kind: "running" } };
|
|
173
|
+
}
|
|
174
|
+
frozenByAgent = false;
|
|
175
|
+
startTicker();
|
|
176
|
+
paint();
|
|
177
|
+
}
|
|
178
|
+
function freeze() {
|
|
179
|
+
frozenByAgent = true;
|
|
180
|
+
state = freezeRun(state);
|
|
181
|
+
stopTicker();
|
|
182
|
+
paint();
|
|
183
|
+
}
|
|
184
|
+
function setAgentBusy(busy) {
|
|
185
|
+
if (agentBusy === busy)
|
|
186
|
+
return;
|
|
187
|
+
agentBusy = busy;
|
|
188
|
+
// The game is strictly opt-in: the panel never auto-expands. The
|
|
189
|
+
// Ctrl-G tease lives in the input placeholder, and the user has to
|
|
190
|
+
// press it themselves to bring up the playfield.
|
|
191
|
+
// When the agent goes idle, snap the panel back to collapsed so
|
|
192
|
+
// the next busy cycle starts invisible rather than re-appearing
|
|
193
|
+
// already expanded. The user opts back in by pressing Ctrl-G.
|
|
194
|
+
if (!busy) {
|
|
195
|
+
expanded = false;
|
|
196
|
+
gameFocused = true;
|
|
197
|
+
}
|
|
198
|
+
syncVisibility();
|
|
199
|
+
paint();
|
|
200
|
+
}
|
|
201
|
+
function handleKey(keyName) {
|
|
202
|
+
if (!expanded)
|
|
203
|
+
return false;
|
|
204
|
+
const action = actionForKey(state, keyName);
|
|
205
|
+
if (action === "ignore")
|
|
206
|
+
return false;
|
|
207
|
+
if (action === "jump") {
|
|
208
|
+
state = applyJump(state);
|
|
209
|
+
}
|
|
210
|
+
else if (action === "start") {
|
|
211
|
+
state = applyStart(state);
|
|
212
|
+
// Starting a run after a death needs the timer running again.
|
|
213
|
+
startTicker();
|
|
214
|
+
}
|
|
215
|
+
paint();
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
function destroy() {
|
|
219
|
+
stopTicker();
|
|
220
|
+
renderer.off("resize", handleResize);
|
|
221
|
+
if (state.highScore > persistedHighScore) {
|
|
222
|
+
saveHighScore(state.highScore);
|
|
223
|
+
persistedHighScore = state.highScore;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function stateNeedsTicking() {
|
|
227
|
+
return (state.phase.kind === "running" ||
|
|
228
|
+
state.phase.kind === "grace" ||
|
|
229
|
+
state.phase.kind === "countdown");
|
|
230
|
+
}
|
|
231
|
+
function startTicker() {
|
|
232
|
+
if (ticker !== undefined)
|
|
233
|
+
return;
|
|
234
|
+
if (!stateNeedsTicking())
|
|
235
|
+
return;
|
|
236
|
+
ticker = setInterval(() => {
|
|
237
|
+
state = tick(state, { random: Math.random, ticksPerSecond: TICKS_PER_SECOND });
|
|
238
|
+
if (state.highScore > persistedHighScore) {
|
|
239
|
+
saveHighScore(state.highScore);
|
|
240
|
+
persistedHighScore = state.highScore;
|
|
241
|
+
}
|
|
242
|
+
if (!stateNeedsTicking())
|
|
243
|
+
stopTicker();
|
|
244
|
+
paint();
|
|
245
|
+
}, TICK_INTERVAL_MS);
|
|
246
|
+
}
|
|
247
|
+
function stopTicker() {
|
|
248
|
+
if (ticker !== undefined) {
|
|
249
|
+
clearInterval(ticker);
|
|
250
|
+
ticker = undefined;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function syncVisibility() {
|
|
254
|
+
const visibleRows = panelVisibleRowCount(expanded, agentBusy);
|
|
255
|
+
for (let i = 0; i < rows.length; i++) {
|
|
256
|
+
rows[i].visible = i < visibleRows;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function paint() {
|
|
260
|
+
// At rest the panel is invisible; skip painting entirely so the row
|
|
261
|
+
// contents stay empty and no chrome leaks into the layout.
|
|
262
|
+
if (!agentBusy) {
|
|
263
|
+
for (const row of rows)
|
|
264
|
+
row.content = "";
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (expanded) {
|
|
268
|
+
const frame = renderExpanded(state, gameFocused);
|
|
269
|
+
for (let i = 0; i < rows.length; i++) {
|
|
270
|
+
rows[i].content = frame[i] ?? "";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Collapsed reserves zero rows; clear any stale content so the
|
|
275
|
+
// pool stays tidy when the next expand reuses it.
|
|
276
|
+
for (const row of rows)
|
|
277
|
+
row.content = "";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
view,
|
|
282
|
+
isExpanded,
|
|
283
|
+
isGameFocused,
|
|
284
|
+
toggle,
|
|
285
|
+
resume,
|
|
286
|
+
freeze,
|
|
287
|
+
setAgentBusy,
|
|
288
|
+
handleKey,
|
|
289
|
+
destroy,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** Compute the current playfield width from the renderer's terminal size.
|
|
293
|
+
* The panel sits in the main layout column to the left of the sidebar, so
|
|
294
|
+
* we deduct the sidebar plus a small chrome margin and let `setFieldWidth`
|
|
295
|
+
* clamp into the supported [MIN_FIELD_WIDTH, MAX_FIELD_WIDTH] range. */
|
|
296
|
+
function computeFieldWidth(renderer) {
|
|
297
|
+
const terminal = renderer.terminalWidth;
|
|
298
|
+
if (!Number.isFinite(terminal) || terminal <= 0)
|
|
299
|
+
return 0; // clamped by setFieldWidth
|
|
300
|
+
return terminal - SIDEBAR_WIDTH - PANEL_CHROME_COLUMNS;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/tui/dino/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GameState } from "./state.js";
|
|
2
|
+
export type DinoAction = "jump" | "start" | "ignore";
|
|
3
|
+
/** Returns the action implied by a key press for the current game state.
|
|
4
|
+
* Returns `"ignore"` for keys the panel should let bubble (so e.g. typing
|
|
5
|
+
* letters while the panel is collapsed never gets eaten). */
|
|
6
|
+
export declare function actionForKey(state: GameState, keyName: string | undefined): DinoAction;
|
|
7
|
+
/** Apply a `jump` action: only takes effect when the dino is grounded so
|
|
8
|
+
* the player cannot double-jump. */
|
|
9
|
+
export declare function applyJump(state: GameState): GameState;
|
|
10
|
+
/** Apply a `start` action: begin a fresh run from `idle`/`gameover`. */
|
|
11
|
+
export declare function applyStart(state: GameState): GameState;
|
|
12
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../../src/tui/dino/input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAQrD;;8DAE8D;AAC9D,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAetF;AAED;qCACqC;AACrC,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAGrD;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAGtD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Keystroke → game action. Pure so the panel can route keys through this
|
|
2
|
+
// without dragging OpenTUI key types into the reducer layer.
|
|
3
|
+
import { JUMP_VELOCITY, startRun } from "./state.js";
|
|
4
|
+
/** Names of keys that, when game is in a state that accepts input, trigger
|
|
5
|
+
* a jump. Arrow-only on purpose: the spacebar belongs to the composer so
|
|
6
|
+
* the user can type a follow-up while the game keeps running in the
|
|
7
|
+
* background. OpenTUI normalizes ArrowUp to `name: "up"`. */
|
|
8
|
+
const JUMP_KEYS = new Set(["up"]);
|
|
9
|
+
/** Returns the action implied by a key press for the current game state.
|
|
10
|
+
* Returns `"ignore"` for keys the panel should let bubble (so e.g. typing
|
|
11
|
+
* letters while the panel is collapsed never gets eaten). */
|
|
12
|
+
export function actionForKey(state, keyName) {
|
|
13
|
+
if (!keyName)
|
|
14
|
+
return "ignore";
|
|
15
|
+
if (state.phase.kind === "idle" || state.phase.kind === "gameover") {
|
|
16
|
+
// Any of the jump keys also acts as "start a run" so the user does not
|
|
17
|
+
// need a separate start key after a death.
|
|
18
|
+
if (JUMP_KEYS.has(keyName) || keyName === "return" || keyName === "enter")
|
|
19
|
+
return "start";
|
|
20
|
+
return "ignore";
|
|
21
|
+
}
|
|
22
|
+
if (state.phase.kind === "running" || state.phase.kind === "grace") {
|
|
23
|
+
if (JUMP_KEYS.has(keyName))
|
|
24
|
+
return "jump";
|
|
25
|
+
return "ignore";
|
|
26
|
+
}
|
|
27
|
+
// countdown / frozen swallow input so the user cannot pre-jump and then
|
|
28
|
+
// land on a cactus the instant the world unfreezes.
|
|
29
|
+
return "ignore";
|
|
30
|
+
}
|
|
31
|
+
/** Apply a `jump` action: only takes effect when the dino is grounded so
|
|
32
|
+
* the player cannot double-jump. */
|
|
33
|
+
export function applyJump(state) {
|
|
34
|
+
if (state.dinoY > 0 || state.dinoVy !== 0)
|
|
35
|
+
return state;
|
|
36
|
+
return { ...state, dinoVy: JUMP_VELOCITY };
|
|
37
|
+
}
|
|
38
|
+
/** Apply a `start` action: begin a fresh run from `idle`/`gameover`. */
|
|
39
|
+
export function applyStart(state) {
|
|
40
|
+
if (state.phase.kind !== "idle" && state.phase.kind !== "gameover")
|
|
41
|
+
return state;
|
|
42
|
+
return startRun(state);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../../../src/tui/dino/input.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,6DAA6D;AAG7D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIrD;;;8DAG8D;AAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAElC;;8DAE8D;AAC9D,MAAM,UAAU,YAAY,CAAC,KAAgB,EAAE,OAA2B;IACxE,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACnE,uEAAuE;QACvE,2CAA2C;QAC3C,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,OAAO;YAAE,OAAO,OAAO,CAAC;QAC1F,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACnE,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC;QAC1C,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,wEAAwE;IACxE,oDAAoD;IACpD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;qCACqC;AACrC,MAAM,UAAU,SAAS,CAAC,KAAgB;IACxC,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AAC7C,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,KAAgB;IACzC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACjF,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../../src/tui/dino/persistence.ts"],"names":[],"mappings":"AAgBA,wBAAgB,aAAa,IAAI,MAAM,CAQtC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAOjD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Reads/writes the dino panel's tiny persisted footprint under
|
|
2
|
+
// `~/.duet/dino/`. Only the high score is persisted: panel open/closed
|
|
3
|
+
// state is intentionally session-local because the spec is that every
|
|
4
|
+
// new busy cycle starts at "hint only" and the user toggles back in.
|
|
5
|
+
// Writes are best-effort: a failed write degrades the next run to the
|
|
6
|
+
// in-memory default but never propagates to the caller, because losing a
|
|
7
|
+
// high score is strictly better than crashing the TUI on a read-only
|
|
8
|
+
// home directory.
|
|
9
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
const DINO_DIR = join(homedir(), ".duet", "dino");
|
|
13
|
+
const HIGHSCORE_FILE = join(DINO_DIR, "highscore");
|
|
14
|
+
export function loadHighScore() {
|
|
15
|
+
try {
|
|
16
|
+
const raw = readFileSync(HIGHSCORE_FILE, "utf8").trim();
|
|
17
|
+
const n = Number.parseInt(raw, 10);
|
|
18
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function saveHighScore(score) {
|
|
25
|
+
try {
|
|
26
|
+
ensureDir(HIGHSCORE_FILE);
|
|
27
|
+
writeFileSync(HIGHSCORE_FILE, `${Math.max(0, Math.floor(score))}\n`, "utf8");
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Best-effort.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function ensureDir(filePath) {
|
|
34
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../../../src/tui/dino/persistence.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,uEAAuE;AACvE,sEAAsE;AACtE,qEAAqE;AACrE,sEAAsE;AACtE,yEAAyE;AACzE,qEAAqE;AACrE,kBAAkB;AAElB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAClD,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AAEnD,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACxD,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACnC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC;QACH,SAAS,CAAC,cAAc,CAAC,CAAC;QAC1B,aAAa,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB;IACjC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type GameState } from "./state.js";
|
|
2
|
+
/** Total rendered rows of the expanded panel: title + 10 playfield rows +
|
|
3
|
+
* status row = 12 rows when expanded. Collapsed = 0 rows — the Ctrl-G
|
|
4
|
+
* tease lives in the input placeholder instead, so the dino takes no
|
|
5
|
+
* vertical space until the user opts in. */
|
|
6
|
+
export declare const EXPANDED_ROWS = 12;
|
|
7
|
+
export declare const COLLAPSED_ROWS = 0;
|
|
8
|
+
export declare const PLAYFIELD_ROWS = 10;
|
|
9
|
+
/** Width of the dino sprite in cells. Anchored at `DINO_X` (left edge). */
|
|
10
|
+
export declare const DINO_WIDTH = 4;
|
|
11
|
+
/** Height of the dino sprite in rows; feet sit on the ground row, so the
|
|
12
|
+
* body extends up `DINO_HEIGHT - 1` rows above ground when grounded. */
|
|
13
|
+
export declare const DINO_HEIGHT = 3;
|
|
14
|
+
/** Full 12-row expanded panel. The phase determines which overlay (idle
|
|
15
|
+
* splash, countdown numerals, dim "agent needs you" hint, gameover
|
|
16
|
+
* banner) is composited on top of the playfield. */
|
|
17
|
+
export declare function renderExpanded(state: GameState, gameFocused?: boolean): string[];
|
|
18
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/tui/dino/render.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEhE;;;6CAG6C;AAC7C,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,2EAA2E;AAC3E,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B;yEACyE;AACzE,eAAO,MAAM,WAAW,IAAI,CAAC;AAc7B;;qDAEqD;AACrD,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,GAAE,OAAc,GAAG,MAAM,EAAE,CAYtF"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// state → string[]. Pure ASCII; no OpenTUI imports so tests can snapshot
|
|
2
|
+
// the rendered frame as plain strings.
|
|
3
|
+
import { DINO_X, GROUND_ROW } from "./state.js";
|
|
4
|
+
/** Total rendered rows of the expanded panel: title + 10 playfield rows +
|
|
5
|
+
* status row = 12 rows when expanded. Collapsed = 0 rows — the Ctrl-G
|
|
6
|
+
* tease lives in the input placeholder instead, so the dino takes no
|
|
7
|
+
* vertical space until the user opts in. */
|
|
8
|
+
export const EXPANDED_ROWS = 12;
|
|
9
|
+
export const COLLAPSED_ROWS = 0;
|
|
10
|
+
export const PLAYFIELD_ROWS = 10;
|
|
11
|
+
/** Width of the dino sprite in cells. Anchored at `DINO_X` (left edge). */
|
|
12
|
+
export const DINO_WIDTH = 4;
|
|
13
|
+
/** Height of the dino sprite in rows; feet sit on the ground row, so the
|
|
14
|
+
* body extends up `DINO_HEIGHT - 1` rows above ground when grounded. */
|
|
15
|
+
export const DINO_HEIGHT = 3;
|
|
16
|
+
// Tiny T-rex sprites in pure ASCII so they line up cleanly in any
|
|
17
|
+
// terminal. The right edge points in the direction of travel: head and
|
|
18
|
+
// snout to the right, tail to the left. Two run poses alternate while
|
|
19
|
+
// grounded so the legs visibly cycle; a third pose is used mid-air.
|
|
20
|
+
//
|
|
21
|
+
// Each sprite is exactly DINO_HEIGHT rows by DINO_WIDTH cols. Empty
|
|
22
|
+
// cells use a literal space so the sprite never accidentally erases
|
|
23
|
+
// neighboring playfield characters.
|
|
24
|
+
const DINO_RUN_A = [" __", " /_)", " /\\ "];
|
|
25
|
+
const DINO_RUN_B = [" __", " /_)", " \\/ "];
|
|
26
|
+
const DINO_JUMP = [" __", " /_)", " || "];
|
|
27
|
+
/** Full 12-row expanded panel. The phase determines which overlay (idle
|
|
28
|
+
* splash, countdown numerals, dim "agent needs you" hint, gameover
|
|
29
|
+
* banner) is composited on top of the playfield. */
|
|
30
|
+
export function renderExpanded(state, gameFocused = true) {
|
|
31
|
+
const rows = buildPlayfield(state);
|
|
32
|
+
const title = renderTitle(state);
|
|
33
|
+
const statusRow = renderStatusRow(state, gameFocused);
|
|
34
|
+
const overlay = renderOverlay(state);
|
|
35
|
+
if (overlay) {
|
|
36
|
+
// Overlay replaces a centered slice of the playfield rows; the dino
|
|
37
|
+
// and obstacles still show around it so the user sees what they will
|
|
38
|
+
// wake up to.
|
|
39
|
+
overlayRows(rows, overlay);
|
|
40
|
+
}
|
|
41
|
+
return [title, ...rows, statusRow];
|
|
42
|
+
}
|
|
43
|
+
function renderTitle(state) {
|
|
44
|
+
const score = pad4(Math.floor(state.score));
|
|
45
|
+
const hi = pad4(state.highScore);
|
|
46
|
+
return ` duet dino · HI ${hi} · ${score}`;
|
|
47
|
+
}
|
|
48
|
+
function renderStatusRow(state, gameFocused) {
|
|
49
|
+
// When the user has Ctrl-G'd into typing mode the panel stays open
|
|
50
|
+
// and the game keeps animating, but the spacebar belongs to the
|
|
51
|
+
// composer. The status row advertises how to take the keys back so
|
|
52
|
+
// the mode is discoverable.
|
|
53
|
+
if (!gameFocused) {
|
|
54
|
+
return " typing mode · Ctrl-G to play · Ctrl-G again to close";
|
|
55
|
+
}
|
|
56
|
+
switch (state.phase.kind) {
|
|
57
|
+
case "idle":
|
|
58
|
+
return " press ↑ to start · Ctrl-G for typing mode";
|
|
59
|
+
case "running":
|
|
60
|
+
return " ↑ to jump · Ctrl-G for typing mode";
|
|
61
|
+
case "grace":
|
|
62
|
+
return " …";
|
|
63
|
+
case "countdown":
|
|
64
|
+
return " resuming…";
|
|
65
|
+
case "frozen":
|
|
66
|
+
return " ▲ agent needs you — answer above";
|
|
67
|
+
case "gameover":
|
|
68
|
+
return " game over · ↑ to retry · Ctrl-G for typing mode";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function buildPlayfield(state) {
|
|
72
|
+
const width = state.fieldWidth;
|
|
73
|
+
const rows = Array.from({ length: PLAYFIELD_ROWS }, () => Array.from({ length: width }, () => " "));
|
|
74
|
+
// Ground line.
|
|
75
|
+
const groundIndex = Math.min(GROUND_ROW, PLAYFIELD_ROWS - 1);
|
|
76
|
+
for (let x = 0; x < width; x++)
|
|
77
|
+
rows[groundIndex][x] = "_";
|
|
78
|
+
// Obstacles. Cactus is drawn as "#" stacked vertically, anchored at the
|
|
79
|
+
// ground line. A 1-cell-wide column keeps collision and rendering in
|
|
80
|
+
// exact agreement.
|
|
81
|
+
for (const o of state.obstacles) {
|
|
82
|
+
const col = Math.round(o.x);
|
|
83
|
+
if (col < 0 || col >= width)
|
|
84
|
+
continue;
|
|
85
|
+
for (let h = 0; h < o.height; h++) {
|
|
86
|
+
const row = groundIndex - 1 - h;
|
|
87
|
+
if (row >= 0)
|
|
88
|
+
rows[row][col] = "#";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Dino. Multi-row T-rex sprite anchored at `DINO_X`, with feet on the
|
|
92
|
+
// ground row when grounded. Floor-rounded `y` so a sub-cell hop still
|
|
93
|
+
// visibly lifts off. The grounded pose alternates between two leg
|
|
94
|
+
// positions so the dino looks like it's running; airborne uses the
|
|
95
|
+
// tucked-legs jump sprite so the user can confirm a jump landed.
|
|
96
|
+
const grounded = state.dinoY === 0;
|
|
97
|
+
const sprite = grounded
|
|
98
|
+
? // Alternate legs every ~2 score units so the cadence reads as
|
|
99
|
+
// running rather than flickering at every frame.
|
|
100
|
+
Math.floor(state.score * 2) % 2 === 0
|
|
101
|
+
? DINO_RUN_A
|
|
102
|
+
: DINO_RUN_B
|
|
103
|
+
: DINO_JUMP;
|
|
104
|
+
const feetRow = groundIndex - Math.max(0, Math.floor(state.dinoY));
|
|
105
|
+
for (let dy = 0; dy < sprite.length; dy++) {
|
|
106
|
+
const targetRow = feetRow - (sprite.length - 1 - dy);
|
|
107
|
+
if (targetRow < 0 || targetRow >= PLAYFIELD_ROWS)
|
|
108
|
+
continue;
|
|
109
|
+
const line = sprite[dy];
|
|
110
|
+
for (let dx = 0; dx < line.length; dx++) {
|
|
111
|
+
const ch = line[dx];
|
|
112
|
+
if (ch === " ")
|
|
113
|
+
continue; // sprite holes leave the playfield intact
|
|
114
|
+
const col = DINO_X + dx;
|
|
115
|
+
if (col < 0 || col >= width)
|
|
116
|
+
continue;
|
|
117
|
+
rows[targetRow][col] = ch;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return rows.map((r) => r.join(""));
|
|
121
|
+
}
|
|
122
|
+
function renderOverlay(state) {
|
|
123
|
+
if (state.phase.kind === "countdown") {
|
|
124
|
+
// Big numeral derived from ticksRemaining. The tick layer counts down
|
|
125
|
+
// in whole-second blocks of `ticksPerSecond` so dividing reproduces 3
|
|
126
|
+
// → 2 → 1 cleanly regardless of the actual cadence.
|
|
127
|
+
const seconds = Math.ceil(state.phase.ticksRemaining / 15);
|
|
128
|
+
return bigNumeral(seconds);
|
|
129
|
+
}
|
|
130
|
+
if (state.phase.kind === "frozen") {
|
|
131
|
+
return ["agent needs you", "answer the prompt above"];
|
|
132
|
+
}
|
|
133
|
+
if (state.phase.kind === "gameover") {
|
|
134
|
+
return ["game over", `score ${Math.floor(state.score)}`];
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
function overlayRows(rows, overlay) {
|
|
139
|
+
const width = rows[0]?.length ?? 0;
|
|
140
|
+
const startRow = Math.max(0, Math.floor((rows.length - overlay.length) / 2));
|
|
141
|
+
for (let i = 0; i < overlay.length; i++) {
|
|
142
|
+
const target = startRow + i;
|
|
143
|
+
if (target >= rows.length)
|
|
144
|
+
break;
|
|
145
|
+
const line = overlay[i];
|
|
146
|
+
const startCol = Math.max(0, Math.floor((width - line.length) / 2));
|
|
147
|
+
const before = rows[target].slice(0, startCol);
|
|
148
|
+
const after = rows[target].slice(startCol + line.length);
|
|
149
|
+
rows[target] = (before + line + after).slice(0, width);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function bigNumeral(n) {
|
|
153
|
+
// Stylized but ASCII-only so it carries the 8-bit feel without leaning
|
|
154
|
+
// on Unicode block characters that render at inconsistent widths.
|
|
155
|
+
const label = n > 0 ? String(n) : "GO!";
|
|
156
|
+
return [`>>> ${label} <<<`];
|
|
157
|
+
}
|
|
158
|
+
function pad4(n) {
|
|
159
|
+
return n.toString().padStart(4, "0");
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../../src/tui/dino/render.ts"],"names":[],"mappings":"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"}
|