@sharpee/sharpee 1.0.0 → 1.1.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.
@@ -1,275 +0,0 @@
1
- /**
2
- * Browser Entry Point for {{STORY_TITLE}}
3
- *
4
- * This file connects your story to the browser UI.
5
- * Generated by: npx sharpee init-browser
6
- */
7
-
8
- import { GameEngine } from '@sharpee/engine';
9
- import { WorldModel, EntityType } from '@sharpee/world-model';
10
- import { Parser } from '@sharpee/parser-en-us';
11
- import { LanguageProvider } from '@sharpee/lang-en-us';
12
- import { PerceptionService } from '@sharpee/stdlib';
13
- import { renderToString } from '@sharpee/text-service';
14
- import { story, config } from './index.js';
15
-
16
- // DOM elements
17
- let statusLocation: HTMLElement | null;
18
- let statusScore: HTMLElement | null;
19
- let textContent: HTMLElement | null;
20
- let mainWindow: HTMLElement | null;
21
- let commandInput: HTMLInputElement | null;
22
-
23
- // Game state
24
- let engine: GameEngine;
25
- let world: WorldModel;
26
- let commandHistory: string[] = [];
27
- let historyIndex = -1;
28
- let currentTurn = 0;
29
- let currentScore = 0;
30
-
31
- /**
32
- * Initialize the game
33
- */
34
- function initializeGame(): void {
35
- // Create world and player
36
- world = new WorldModel();
37
- const player = world.createEntity('player', EntityType.ACTOR);
38
- world.setPlayer(player.id);
39
-
40
- // Create parser and language
41
- const language = new LanguageProvider();
42
- const parser = new Parser(language);
43
-
44
- // Extend parser and language with story-specific vocabulary
45
- if (story.extendParser) {
46
- story.extendParser(parser);
47
- }
48
- if (story.extendLanguage) {
49
- story.extendLanguage(language);
50
- }
51
-
52
- // Create perception service
53
- const perceptionService = new PerceptionService();
54
-
55
- // Create engine
56
- engine = new GameEngine({
57
- world,
58
- player,
59
- parser,
60
- language,
61
- perceptionService,
62
- });
63
-
64
- // Set up event handlers
65
- engine.on('text:output', (blocks, turn) => {
66
- displayText(renderToString(blocks));
67
- currentTurn = turn;
68
- updateStatusLine();
69
- });
70
-
71
- engine.on('event', (event: any) => {
72
- // Track score changes
73
- if (event.type === 'game.score_changed' && event.data) {
74
- currentScore = event.data.newScore ?? currentScore;
75
- updateStatusLine();
76
- }
77
- });
78
-
79
- // Set the story
80
- engine.setStory(story);
81
- }
82
-
83
- /**
84
- * Set up DOM elements and event handlers
85
- */
86
- function setupDOM(): void {
87
- statusLocation = document.getElementById('location-name');
88
- statusScore = document.getElementById('score-turns');
89
- textContent = document.getElementById('text-content');
90
- mainWindow = document.getElementById('main-window');
91
- commandInput = document.getElementById('command-input') as HTMLInputElement;
92
-
93
- if (!commandInput) {
94
- console.error('Command input element not found');
95
- return;
96
- }
97
-
98
- // Handle keyboard input
99
- commandInput.addEventListener('keydown', (e: KeyboardEvent) => {
100
- if (e.key === 'Enter') {
101
- handleCommand();
102
- } else if (e.key === 'ArrowUp') {
103
- e.preventDefault();
104
- navigateHistory(-1);
105
- } else if (e.key === 'ArrowDown') {
106
- e.preventDefault();
107
- navigateHistory(1);
108
- }
109
- });
110
-
111
- // Keep focus on input
112
- document.addEventListener('click', () => {
113
- if (commandInput && !commandInput.disabled) {
114
- commandInput.focus();
115
- }
116
- });
117
- }
118
-
119
- /**
120
- * Handle command submission
121
- */
122
- async function handleCommand(): Promise<void> {
123
- if (!commandInput) return;
124
-
125
- const command = commandInput.value.trim();
126
- if (!command) return;
127
-
128
- // Add to history
129
- commandHistory.push(command);
130
- historyIndex = commandHistory.length;
131
-
132
- // Clear input
133
- commandInput.value = '';
134
-
135
- // Display command echo
136
- displayCommand(command);
137
-
138
- // Execute command
139
- try {
140
- await engine.executeTurn(command);
141
- } catch (error) {
142
- const message = error instanceof Error ? error.message : String(error);
143
- displayText(`[Error: ${message}]`);
144
- }
145
- }
146
-
147
- /**
148
- * Navigate command history
149
- */
150
- function navigateHistory(direction: number): void {
151
- if (!commandInput) return;
152
-
153
- const newIndex = historyIndex + direction;
154
-
155
- if (newIndex < 0) return;
156
-
157
- if (newIndex >= commandHistory.length) {
158
- historyIndex = commandHistory.length;
159
- commandInput.value = '';
160
- return;
161
- }
162
-
163
- historyIndex = newIndex;
164
- commandInput.value = commandHistory[historyIndex];
165
-
166
- // Move cursor to end
167
- commandInput.setSelectionRange(
168
- commandInput.value.length,
169
- commandInput.value.length
170
- );
171
- }
172
-
173
- /**
174
- * Display text in the main window
175
- */
176
- function displayText(text: string): void {
177
- if (!textContent) return;
178
-
179
- // Split on double newlines to get paragraphs
180
- const paragraphs = text.split(/\n\n+/);
181
-
182
- for (const para of paragraphs) {
183
- const trimmed = para.trim();
184
- if (trimmed) {
185
- const p = document.createElement('p');
186
- p.style.whiteSpace = 'pre-line';
187
- p.textContent = trimmed;
188
- textContent.appendChild(p);
189
- }
190
- }
191
-
192
- scrollToBottom();
193
- }
194
-
195
- /**
196
- * Display command echo
197
- */
198
- function displayCommand(command: string): void {
199
- if (!textContent) return;
200
-
201
- const div = document.createElement('div');
202
- div.className = 'command-echo';
203
- div.textContent = `> ${command}`;
204
- textContent.appendChild(div);
205
-
206
- scrollToBottom();
207
- }
208
-
209
- /**
210
- * Update the status line
211
- */
212
- function updateStatusLine(): void {
213
- const player = world.getPlayer();
214
- let locationName = '';
215
-
216
- if (player) {
217
- const locationId = world.getLocation(player.id);
218
- if (locationId) {
219
- const room = world.getEntity(locationId);
220
- if (room) {
221
- locationName = room.name || 'Unknown';
222
- }
223
- }
224
- }
225
-
226
- if (statusLocation) {
227
- statusLocation.textContent = locationName;
228
- }
229
-
230
- if (statusScore) {
231
- statusScore.textContent = `Score: ${currentScore} | Turns: ${currentTurn}`;
232
- }
233
- }
234
-
235
- /**
236
- * Scroll main window to bottom
237
- */
238
- function scrollToBottom(): void {
239
- if (mainWindow) {
240
- mainWindow.scrollTop = mainWindow.scrollHeight;
241
- }
242
- }
243
-
244
- /**
245
- * Start the game
246
- */
247
- async function start(): Promise<void> {
248
- console.log('=== {{STORY_TITLE}} BROWSER START ===');
249
-
250
- try {
251
- setupDOM();
252
- initializeGame();
253
-
254
- // Start the engine
255
- await engine.start();
256
-
257
- // Initial look
258
- await engine.executeTurn('look');
259
-
260
- // Focus input
261
- if (commandInput) {
262
- commandInput.focus();
263
- }
264
- } catch (error) {
265
- console.error('=== STARTUP ERROR ===', error);
266
- displayText(`[Startup Error: ${error}]`);
267
- }
268
- }
269
-
270
- // Start when DOM is ready
271
- if (document.readyState === 'loading') {
272
- document.addEventListener('DOMContentLoaded', start);
273
- } else {
274
- start();
275
- }
@@ -1,75 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>{{STORY_TITLE}}</title>
7
- <link rel="stylesheet" href="styles.css">
8
- </head>
9
- <body>
10
- <div id="game-container">
11
- <div id="status-line">
12
- <span id="location-name"></span>
13
- <span id="score-turns">Score: 0 | Turns: 0</span>
14
- </div>
15
- <div id="main-window">
16
- <div id="text-content"></div>
17
- </div>
18
- <div id="input-area">
19
- <span class="prompt">&gt;</span>
20
- <input id="command-input" type="text"
21
- autocomplete="off" autocapitalize="none"
22
- spellcheck="false" autofocus>
23
- </div>
24
- </div>
25
-
26
- <!-- Save/Restore Modal Dialogs -->
27
- <div id="modal-overlay" class="modal-hidden">
28
- <!-- Save Dialog -->
29
- <div id="save-dialog" class="modal-dialog modal-hidden">
30
- <div class="modal-title">SAVE GAME</div>
31
- <div class="modal-content">
32
- <div class="save-input-row">
33
- <label for="save-name-input">Save name:</label>
34
- <input type="text" id="save-name-input" maxlength="30"
35
- autocomplete="off" spellcheck="false">
36
- </div>
37
- <div class="saves-list-label">Existing saves (click to overwrite):</div>
38
- <div id="save-slots-list" class="saves-list"></div>
39
- </div>
40
- <div class="modal-buttons">
41
- <button id="save-confirm-btn" class="modal-btn">Save</button>
42
- <button id="save-cancel-btn" class="modal-btn">Cancel</button>
43
- </div>
44
- </div>
45
-
46
- <!-- Restore Dialog -->
47
- <div id="restore-dialog" class="modal-dialog modal-hidden">
48
- <div class="modal-title">RESTORE GAME</div>
49
- <div class="modal-content">
50
- <div class="saves-list-label">Select a saved game:</div>
51
- <div id="restore-slots-list" class="saves-list"></div>
52
- <div id="no-saves-message" class="no-saves-message modal-hidden">No saved games found.</div>
53
- </div>
54
- <div class="modal-buttons">
55
- <button id="restore-confirm-btn" class="modal-btn">Restore</button>
56
- <button id="restore-cancel-btn" class="modal-btn">Cancel</button>
57
- </div>
58
- </div>
59
-
60
- <!-- Startup Dialog (continue saved game?) -->
61
- <div id="startup-dialog" class="modal-dialog modal-hidden">
62
- <div class="modal-title">CONTINUE GAME?</div>
63
- <div class="modal-content">
64
- <p id="startup-save-info" class="startup-info"></p>
65
- <p class="startup-question">Continue where you left off?</p>
66
- </div>
67
- <div class="modal-buttons">
68
- <button id="startup-continue-btn" class="modal-btn">Continue</button>
69
- <button id="startup-new-btn" class="modal-btn">New Game</button>
70
- </div>
71
- </div>
72
- </div>
73
- <script src="{{STORY_ID}}.js"></script>
74
- </body>
75
- </html>