arbiter-ai 1.0.0
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 +41 -0
- package/assets/jerom_16x16.png +0 -0
- package/dist/arbiter.d.ts +43 -0
- package/dist/arbiter.js +486 -0
- package/dist/context-analyzer.d.ts +15 -0
- package/dist/context-analyzer.js +603 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +165 -0
- package/dist/orchestrator.d.ts +31 -0
- package/dist/orchestrator.js +227 -0
- package/dist/router.d.ts +187 -0
- package/dist/router.js +1135 -0
- package/dist/router.test.d.ts +15 -0
- package/dist/router.test.js +95 -0
- package/dist/session-persistence.d.ts +9 -0
- package/dist/session-persistence.js +63 -0
- package/dist/session-persistence.test.d.ts +1 -0
- package/dist/session-persistence.test.js +165 -0
- package/dist/sound.d.ts +31 -0
- package/dist/sound.js +50 -0
- package/dist/state.d.ts +72 -0
- package/dist/state.js +107 -0
- package/dist/state.test.d.ts +1 -0
- package/dist/state.test.js +194 -0
- package/dist/test-headless.d.ts +1 -0
- package/dist/test-headless.js +155 -0
- package/dist/tui/index.d.ts +14 -0
- package/dist/tui/index.js +17 -0
- package/dist/tui/layout.d.ts +30 -0
- package/dist/tui/layout.js +200 -0
- package/dist/tui/render.d.ts +57 -0
- package/dist/tui/render.js +266 -0
- package/dist/tui/scene.d.ts +64 -0
- package/dist/tui/scene.js +366 -0
- package/dist/tui/screens/CharacterSelect-termkit.d.ts +18 -0
- package/dist/tui/screens/CharacterSelect-termkit.js +216 -0
- package/dist/tui/screens/ForestIntro-termkit.d.ts +15 -0
- package/dist/tui/screens/ForestIntro-termkit.js +856 -0
- package/dist/tui/screens/GitignoreCheck-termkit.d.ts +14 -0
- package/dist/tui/screens/GitignoreCheck-termkit.js +185 -0
- package/dist/tui/screens/TitleScreen-termkit.d.ts +14 -0
- package/dist/tui/screens/TitleScreen-termkit.js +132 -0
- package/dist/tui/screens/index.d.ts +9 -0
- package/dist/tui/screens/index.js +10 -0
- package/dist/tui/tileset.d.ts +97 -0
- package/dist/tui/tileset.js +237 -0
- package/dist/tui/tui-termkit.d.ts +34 -0
- package/dist/tui/tui-termkit.js +2602 -0
- package/dist/tui/types.d.ts +41 -0
- package/dist/tui/types.js +4 -0
- package/package.json +71 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character Selection Screen (terminal-kit version)
|
|
3
|
+
*
|
|
4
|
+
* Displays 8 human character tiles for the user to choose from.
|
|
5
|
+
* Uses arrow keys for selection and Enter to confirm.
|
|
6
|
+
* Uses terminal-kit with Strategy 5 (minimal redraws) for flicker-free rendering.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Shows the character selection screen using terminal-kit with Strategy 5 (minimal redraws).
|
|
10
|
+
*
|
|
11
|
+
* @returns Promise<number> - The selected tile index (190-197)
|
|
12
|
+
*/
|
|
13
|
+
export interface CharacterSelectResult {
|
|
14
|
+
character: number;
|
|
15
|
+
skipIntro: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function showCharacterSelect(): Promise<CharacterSelectResult>;
|
|
18
|
+
export default showCharacterSelect;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character Selection Screen (terminal-kit version)
|
|
3
|
+
*
|
|
4
|
+
* Displays 8 human character tiles for the user to choose from.
|
|
5
|
+
* Uses arrow keys for selection and Enter to confirm.
|
|
6
|
+
* Uses terminal-kit with Strategy 5 (minimal redraws) for flicker-free rendering.
|
|
7
|
+
*/
|
|
8
|
+
import termKit from 'terminal-kit';
|
|
9
|
+
import { playSfx } from '../../sound.js';
|
|
10
|
+
import { CHAR_HEIGHT, compositeTiles, compositeWithFocus, extractTile, loadTileset, RESET, renderTile, TILE, TILE_SIZE, } from '../tileset.js';
|
|
11
|
+
const term = termKit.terminal;
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Constants
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Human character tile indices (190-197)
|
|
16
|
+
const CHARACTER_TILES = [
|
|
17
|
+
TILE.HUMAN_1,
|
|
18
|
+
TILE.HUMAN_2,
|
|
19
|
+
TILE.HUMAN_3,
|
|
20
|
+
TILE.HUMAN_4,
|
|
21
|
+
TILE.HUMAN_5,
|
|
22
|
+
TILE.HUMAN_6,
|
|
23
|
+
TILE.HUMAN_7,
|
|
24
|
+
TILE.HUMAN_8,
|
|
25
|
+
];
|
|
26
|
+
// Character names for each sprite
|
|
27
|
+
const CHARACTER_NAMES = [
|
|
28
|
+
'Adventurer',
|
|
29
|
+
'Rogue',
|
|
30
|
+
'Ranger',
|
|
31
|
+
'Swordsman',
|
|
32
|
+
'Dwarf',
|
|
33
|
+
'Knight',
|
|
34
|
+
'Shadow',
|
|
35
|
+
'Wizard',
|
|
36
|
+
];
|
|
37
|
+
// Layout constants
|
|
38
|
+
const TILE_SPACING = 2; // Space between tiles (characters)
|
|
39
|
+
const TILE_DISPLAY_WIDTH = TILE_SIZE; // 16 chars per tile
|
|
40
|
+
// ANSI codes
|
|
41
|
+
const BOLD = '\x1b[1m';
|
|
42
|
+
const DIM = '\x1b[2m';
|
|
43
|
+
const YELLOW = '\x1b[33m';
|
|
44
|
+
const CYAN = '\x1b[36m';
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Rendering Functions
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Render all character tiles as an array of ANSI strings (one per row).
|
|
50
|
+
* Characters are displayed in a horizontal row with the selected one highlighted.
|
|
51
|
+
* Character tiles are rendered with transparency preserved (no background compositing).
|
|
52
|
+
*/
|
|
53
|
+
function renderCharacterRow(tileset, selectedIndex) {
|
|
54
|
+
// Extract focus tile
|
|
55
|
+
const focusTile = extractTile(tileset, TILE.FOCUS);
|
|
56
|
+
// Extract grass tile for background
|
|
57
|
+
const grassTile = extractTile(tileset, TILE.GRASS);
|
|
58
|
+
// Extract and render each character tile
|
|
59
|
+
const renderedTiles = [];
|
|
60
|
+
for (let i = 0; i < CHARACTER_TILES.length; i++) {
|
|
61
|
+
let charPixels = extractTile(tileset, CHARACTER_TILES[i]);
|
|
62
|
+
// Composite character on grass background
|
|
63
|
+
charPixels = compositeTiles(charPixels, grassTile, 1);
|
|
64
|
+
// Apply focus overlay to selected character
|
|
65
|
+
if (i === selectedIndex) {
|
|
66
|
+
charPixels = compositeWithFocus(charPixels, focusTile);
|
|
67
|
+
}
|
|
68
|
+
renderedTiles.push(renderTile(charPixels));
|
|
69
|
+
}
|
|
70
|
+
// Build output: combine all tiles horizontally for each row
|
|
71
|
+
const spacing = ' '.repeat(TILE_SPACING);
|
|
72
|
+
const lines = [];
|
|
73
|
+
for (let row = 0; row < CHAR_HEIGHT; row++) {
|
|
74
|
+
let line = '';
|
|
75
|
+
for (let charIdx = 0; charIdx < renderedTiles.length; charIdx++) {
|
|
76
|
+
line += renderedTiles[charIdx][row];
|
|
77
|
+
if (charIdx < renderedTiles.length - 1) {
|
|
78
|
+
line += spacing;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
lines.push(line);
|
|
82
|
+
}
|
|
83
|
+
return lines;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Calculate the total width of the character row in characters.
|
|
87
|
+
*/
|
|
88
|
+
function getRowWidth() {
|
|
89
|
+
return CHARACTER_TILES.length * TILE_DISPLAY_WIDTH + (CHARACTER_TILES.length - 1) * TILE_SPACING;
|
|
90
|
+
}
|
|
91
|
+
export async function showCharacterSelect() {
|
|
92
|
+
// Load tileset before entering the Promise
|
|
93
|
+
const tileset = await loadTileset();
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
// Initialize terminal
|
|
96
|
+
term.fullscreen(true);
|
|
97
|
+
term.hideCursor();
|
|
98
|
+
term.grabInput({ mouse: 'button' });
|
|
99
|
+
// State
|
|
100
|
+
let selectedIndex = 0;
|
|
101
|
+
let lastSelectedIndex = -1; // For change detection
|
|
102
|
+
// Get terminal dimensions
|
|
103
|
+
let width = 180;
|
|
104
|
+
let height = 50;
|
|
105
|
+
if (typeof term.width === 'number' && Number.isFinite(term.width) && term.width > 0) {
|
|
106
|
+
width = term.width;
|
|
107
|
+
}
|
|
108
|
+
if (typeof term.height === 'number' && Number.isFinite(term.height) && term.height > 0) {
|
|
109
|
+
height = term.height;
|
|
110
|
+
}
|
|
111
|
+
// Calculate centering offsets
|
|
112
|
+
const rowWidth = getRowWidth();
|
|
113
|
+
const contentHeight = CHAR_HEIGHT + 8; // tiles + title + indicator + name + instructions
|
|
114
|
+
const startX = Math.max(1, Math.floor((width - rowWidth) / 2));
|
|
115
|
+
const startY = Math.max(1, Math.floor((height - contentHeight) / 2));
|
|
116
|
+
/**
|
|
117
|
+
* Draw the screen (only if selection changed)
|
|
118
|
+
*/
|
|
119
|
+
function drawScreen() {
|
|
120
|
+
if (selectedIndex === lastSelectedIndex)
|
|
121
|
+
return;
|
|
122
|
+
lastSelectedIndex = selectedIndex;
|
|
123
|
+
// Clear screen on first draw
|
|
124
|
+
if (lastSelectedIndex === -1) {
|
|
125
|
+
term.clear();
|
|
126
|
+
}
|
|
127
|
+
// Title lines
|
|
128
|
+
const title1 = 'Your journey to the Arbiter begins.';
|
|
129
|
+
const title2 = 'Choose wisely. The forest does not forgive the undiscerning.';
|
|
130
|
+
// Title 1 (yellow)
|
|
131
|
+
term.moveTo(Math.max(1, Math.floor((width - title1.length) / 2)), startY);
|
|
132
|
+
process.stdout.write(`${BOLD}${YELLOW}${title1}${RESET}`);
|
|
133
|
+
// Title 2 (yellow)
|
|
134
|
+
term.moveTo(Math.max(1, Math.floor((width - title2.length) / 2)), startY + 1);
|
|
135
|
+
process.stdout.write(`${BOLD}${YELLOW}${title2}${RESET}`);
|
|
136
|
+
// Render character tiles
|
|
137
|
+
const characterLines = renderCharacterRow(tileset, selectedIndex);
|
|
138
|
+
const tilesStartY = startY + 3;
|
|
139
|
+
for (let i = 0; i < characterLines.length; i++) {
|
|
140
|
+
term.moveTo(startX, tilesStartY + i);
|
|
141
|
+
process.stdout.write(characterLines[i] + RESET);
|
|
142
|
+
}
|
|
143
|
+
// Character name (centered below tiles, with padding)
|
|
144
|
+
const nameY = tilesStartY + CHAR_HEIGHT + 2;
|
|
145
|
+
const characterName = CHARACTER_NAMES[selectedIndex];
|
|
146
|
+
// Clear the line first to remove previous name
|
|
147
|
+
term.moveTo(1, nameY);
|
|
148
|
+
process.stdout.write(' '.repeat(width));
|
|
149
|
+
term.moveTo(Math.max(1, Math.floor((width - characterName.length) / 2)), nameY);
|
|
150
|
+
process.stdout.write(`${BOLD}${CYAN}${characterName}${RESET}`);
|
|
151
|
+
// Instructions at bottom
|
|
152
|
+
const instructionY = nameY + 2;
|
|
153
|
+
const instructions = '[LEFT/RIGHT or H/L] Navigate [ENTER] Select [SPACE] Skip intro [Q] Exit';
|
|
154
|
+
term.moveTo(Math.max(1, Math.floor((width - instructions.length) / 2)), instructionY);
|
|
155
|
+
process.stdout.write(`${DIM}${instructions}${RESET}`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Cleanup and restore terminal
|
|
159
|
+
*/
|
|
160
|
+
function cleanup() {
|
|
161
|
+
term.removeAllListeners('key');
|
|
162
|
+
term.grabInput(false);
|
|
163
|
+
term.fullscreen(false);
|
|
164
|
+
term.hideCursor(false);
|
|
165
|
+
}
|
|
166
|
+
// Initial draw
|
|
167
|
+
term.clear();
|
|
168
|
+
drawScreen();
|
|
169
|
+
// Handle keyboard input
|
|
170
|
+
term.on('key', (key) => {
|
|
171
|
+
// Navigation
|
|
172
|
+
if (key === 'LEFT' || key === 'h') {
|
|
173
|
+
playSfx('menuLeft');
|
|
174
|
+
// Move selection left (wrap around)
|
|
175
|
+
selectedIndex = (selectedIndex - 1 + CHARACTER_TILES.length) % CHARACTER_TILES.length;
|
|
176
|
+
drawScreen();
|
|
177
|
+
}
|
|
178
|
+
else if (key === 'RIGHT' || key === 'l') {
|
|
179
|
+
playSfx('menuRight');
|
|
180
|
+
// Move selection right (wrap around)
|
|
181
|
+
selectedIndex = (selectedIndex + 1) % CHARACTER_TILES.length;
|
|
182
|
+
drawScreen();
|
|
183
|
+
}
|
|
184
|
+
else if (key === 'UP' || key === 'k') {
|
|
185
|
+
playSfx('menuLeft');
|
|
186
|
+
// Up moves to previous row (4 characters per row for grid layout)
|
|
187
|
+
selectedIndex = (selectedIndex - 4 + CHARACTER_TILES.length) % CHARACTER_TILES.length;
|
|
188
|
+
drawScreen();
|
|
189
|
+
}
|
|
190
|
+
else if (key === 'DOWN' || key === 'j') {
|
|
191
|
+
playSfx('menuRight');
|
|
192
|
+
// Down moves to next row (4 characters per row for grid layout)
|
|
193
|
+
selectedIndex = (selectedIndex + 4) % CHARACTER_TILES.length;
|
|
194
|
+
drawScreen();
|
|
195
|
+
}
|
|
196
|
+
else if (key === 'ENTER') {
|
|
197
|
+
playSfx('menuSelect');
|
|
198
|
+
// Confirm selection, go to path intro
|
|
199
|
+
cleanup();
|
|
200
|
+
resolve({ character: CHARACTER_TILES[selectedIndex], skipIntro: false });
|
|
201
|
+
}
|
|
202
|
+
else if (key === ' ') {
|
|
203
|
+
playSfx('menuSelect');
|
|
204
|
+
// Skip intro, go straight to arbiter
|
|
205
|
+
cleanup();
|
|
206
|
+
resolve({ character: CHARACTER_TILES[selectedIndex], skipIntro: true });
|
|
207
|
+
}
|
|
208
|
+
else if (key === 'q' || key === 'CTRL_C' || key === 'CTRL_Z') {
|
|
209
|
+
// Exit application
|
|
210
|
+
cleanup();
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
export default showCharacterSelect;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest Intro Screen (terminal-kit version)
|
|
3
|
+
*
|
|
4
|
+
* A narrative intro screen between character select and main TUI.
|
|
5
|
+
* Player controls their character with arrow keys to walk through the forest.
|
|
6
|
+
* Uses terminal-kit with Strategy 5 (minimal redraws) for flicker-free rendering.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Shows the forest intro screen using terminal-kit with Strategy 5 (minimal redraws)
|
|
10
|
+
*
|
|
11
|
+
* @param selectedCharacter - The tile index of the selected character (190-197)
|
|
12
|
+
* @returns Promise<'success' | 'death'> - 'success' when player exits right after seeing sign, 'death' if they die
|
|
13
|
+
*/
|
|
14
|
+
export declare function showForestIntro(selectedCharacter: number): Promise<'success' | 'death'>;
|
|
15
|
+
export default showForestIntro;
|