create-asciitorium 0.1.42 → 0.1.44
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.
|
@@ -0,0 +1,1563 @@
|
|
|
1
|
+
# ASCIITORIUM Quick Reference
|
|
2
|
+
|
|
3
|
+
> **For LLMs**: This document provides correct usage patterns for ASCIITORIUM components. Always refer to the examples here rather than assuming React-like conventions.
|
|
4
|
+
|
|
5
|
+
## Quick Index
|
|
6
|
+
|
|
7
|
+
**Core Concepts:**
|
|
8
|
+
|
|
9
|
+
- [State Management](#state-management)
|
|
10
|
+
- [JSX & Props](#jsx--props)
|
|
11
|
+
- [Component Lifecycle](#component-lifecycle)
|
|
12
|
+
|
|
13
|
+
**Layout:**
|
|
14
|
+
|
|
15
|
+
- [Row](#row)
|
|
16
|
+
- [Column](#column)
|
|
17
|
+
|
|
18
|
+
**UI Components:**
|
|
19
|
+
|
|
20
|
+
- [Text](#text)
|
|
21
|
+
- [Button](#button)
|
|
22
|
+
- [Select](#select)
|
|
23
|
+
- [Switch](#switch)
|
|
24
|
+
- [TextInput](#textinput)
|
|
25
|
+
- [Art](#art)
|
|
26
|
+
|
|
27
|
+
**Game Components:**
|
|
28
|
+
|
|
29
|
+
- [GameWorld](#gameworld)
|
|
30
|
+
- [MapView](#mapview)
|
|
31
|
+
- [FirstPersonView](#firstpersonview)
|
|
32
|
+
|
|
33
|
+
**Special:**
|
|
34
|
+
|
|
35
|
+
- [Keybind](#keybind)
|
|
36
|
+
- [PerfMonitor](#perfmonitor)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## State Management
|
|
41
|
+
|
|
42
|
+
### Basic Usage
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { State } from 'asciitorium';
|
|
46
|
+
|
|
47
|
+
// Create reactive state
|
|
48
|
+
const count = new State(0);
|
|
49
|
+
|
|
50
|
+
// Read value
|
|
51
|
+
console.log(count);
|
|
52
|
+
|
|
53
|
+
// Update value (triggers reactivity)
|
|
54
|
+
count.value = 42;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Props
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Description |
|
|
60
|
+
| -------------- | ---- | ------------------- |
|
|
61
|
+
| `initialValue` | `T` | Initial state value |
|
|
62
|
+
|
|
63
|
+
### Common Patterns
|
|
64
|
+
|
|
65
|
+
**Pattern 1: Counter**
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
const count = new State(0);
|
|
69
|
+
|
|
70
|
+
<Button onClick={() => count.value++}>Increment: {count}</Button>;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Pattern 2: Toggle**
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const isVisible = new State(false);
|
|
77
|
+
|
|
78
|
+
<Keybind
|
|
79
|
+
keyBinding="p"
|
|
80
|
+
action={() => isVisible.value = !isVisible.value}
|
|
81
|
+
/>
|
|
82
|
+
<PerfMonitor visible={isVisible} />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Pattern 3: Conditional Rendering**
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const showDebug = new State(false);
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
showDebug.value && <Text>Debug info here</Text>;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Common Mistakes
|
|
96
|
+
|
|
97
|
+
❌ **WRONG** - Using React's useState
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const [count, setCount] = useState(0); // NO! This is React, not ASCIITORIUM
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
✅ **CORRECT** - Using ASCIITORIUM State
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
const count = new State(0);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
❌ **WRONG** - Forgetting .value
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
count++; // NO! count is a State object
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
✅ **CORRECT** - Using .value
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
count.value++;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## JSX & Props
|
|
124
|
+
|
|
125
|
+
### Core Concepts
|
|
126
|
+
|
|
127
|
+
ASCIITORIUM uses custom JSX runtime. Components accept props either:
|
|
128
|
+
|
|
129
|
+
1. Via explicit props (e.g., `content="text"`)
|
|
130
|
+
2. Via JSX children (e.g., `<Text>text</Text>`)
|
|
131
|
+
|
|
132
|
+
### Common Prop Patterns
|
|
133
|
+
|
|
134
|
+
**Sizing:**
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
width={40} // Fixed width
|
|
138
|
+
height={10} // Fixed height
|
|
139
|
+
width="fill" // Fill parent
|
|
140
|
+
// Omit width/height for auto-sizing (or use 'auto', equivalent)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Positioning:**
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
position={{ x: 5, y: 10, z: 10 }} // Absolute positioning, z is optional
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Styling:**
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
border={true} // Show border
|
|
153
|
+
gap={1} // Spacing
|
|
154
|
+
align="center" // Alignment (layout-specific)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Common Mistakes
|
|
158
|
+
|
|
159
|
+
❌ **WRONG** - Using className or style objects
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<Text className="my-class" style={{ color: 'red' }}>
|
|
163
|
+
NO!
|
|
164
|
+
</Text>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
✅ **CORRECT** - Using ASCIITORIUM props
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<Text border={true} width={40}>
|
|
171
|
+
YES!
|
|
172
|
+
</Text>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Row
|
|
178
|
+
|
|
179
|
+
### Basic Usage
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<Row gap={2} align="center">
|
|
183
|
+
<Button>One</Button>
|
|
184
|
+
<Button>Two</Button>
|
|
185
|
+
<Button>Three</Button>
|
|
186
|
+
</Row>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Props
|
|
190
|
+
|
|
191
|
+
| Prop | Type | Default | Description |
|
|
192
|
+
| -------- | ------------------------------- | -------- | ---------------------- |
|
|
193
|
+
| `gap` | `number` | `0` | Space between children |
|
|
194
|
+
| `align` | `'top' \| 'center' \| 'bottom'` | `'top'` | Vertical alignment |
|
|
195
|
+
| `width` | `number \| 'fill'` | `'fill'` | Component width |
|
|
196
|
+
| `height` | `number \| 'fill'` | `'auto'` | Component height |
|
|
197
|
+
|
|
198
|
+
### Common Patterns
|
|
199
|
+
|
|
200
|
+
**Pattern 1: Button Row**
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
<Row gap={2} align="center">
|
|
204
|
+
<Button onClick={() => save()}>Save</Button>
|
|
205
|
+
<Button onClick={() => cancel()}>Cancel</Button>
|
|
206
|
+
</Row>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Pattern 2: Mixed Content**
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<Row gap={1}>
|
|
213
|
+
<Text width={20}>Label:</Text>
|
|
214
|
+
<TextInput width={30} value={inputState} />
|
|
215
|
+
</Row>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Column
|
|
221
|
+
|
|
222
|
+
### Basic Usage
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
<Column gap={1} align="center" width="100%">
|
|
226
|
+
<Text>Header</Text>
|
|
227
|
+
<Text>Content</Text>
|
|
228
|
+
<Text>Footer</Text>
|
|
229
|
+
</Column>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Props
|
|
233
|
+
|
|
234
|
+
| Prop | Type | Default | Description |
|
|
235
|
+
| -------- | ------------------------------- | -------- | ----------------------------------------------- |
|
|
236
|
+
| `gap` | `number` | `0` | Space between children |
|
|
237
|
+
| `align` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment |
|
|
238
|
+
| `width` | `number \| 'fill' \| '100%'` | - | Auto-sizes to children (omit) or set explicitly |
|
|
239
|
+
| `height` | `number \| 'fill'` | `'auto'` | Component height |
|
|
240
|
+
|
|
241
|
+
### Common Patterns
|
|
242
|
+
|
|
243
|
+
**Pattern 1: Centered Layout**
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<Column align="center" width="100%" gap={2}>
|
|
247
|
+
<Art src="logo" />
|
|
248
|
+
<Text>Welcome</Text>
|
|
249
|
+
<Button>Start</Button>
|
|
250
|
+
</Column>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Pattern 2: Form Layout**
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
<Column gap={1}>
|
|
257
|
+
<Text>Username:</Text>
|
|
258
|
+
<TextInput value={username} />
|
|
259
|
+
<Text>Password:</Text>
|
|
260
|
+
<TextInput value={password} />
|
|
261
|
+
<Button>Login</Button>
|
|
262
|
+
</Column>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Text
|
|
268
|
+
|
|
269
|
+
### Basic Usage
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
// Using JSX children
|
|
273
|
+
<Text>Hello, world!</Text>
|
|
274
|
+
|
|
275
|
+
// Using content prop
|
|
276
|
+
<Text content="Hello, world!" />
|
|
277
|
+
|
|
278
|
+
// Using State
|
|
279
|
+
<Text>{myState}</Text>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Props
|
|
283
|
+
|
|
284
|
+
| Prop | Type | Default | Description |
|
|
285
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------- |
|
|
286
|
+
| `content` | `string \| State<any>` | - | Text content (alternative to children) |
|
|
287
|
+
| `children` | `string \| State<any>` | - | JSX children |
|
|
288
|
+
| `textAlign` | `'top-left' \| 'top' \| 'top-right' \| 'left' \| 'center' \| 'right' \| 'bottom-left' \| 'bottom' \| 'bottom-right'` | `'top-left'` | Text alignment within box |
|
|
289
|
+
| `width` | `number \| 'fill' \| 'auto'` | - | Component width (omit for auto-sizing to content) |
|
|
290
|
+
| `height` | `number \| 'auto'` | - | Component height (omit for auto-sizing to wrapped lines) |
|
|
291
|
+
| `border` | `boolean` | `false` | Show border |
|
|
292
|
+
| `scrollable` | `boolean` | `false` | Enable scrolling (arrow keys) |
|
|
293
|
+
| `wrap` | `boolean` | `true` | Enable text wrapping |
|
|
294
|
+
| `typewriter` | `boolean` | `false` | Typewriter reveal effect |
|
|
295
|
+
| `typewriterSpeed` | `number` | `20` | Characters per second for typewriter |
|
|
296
|
+
|
|
297
|
+
### Common Patterns
|
|
298
|
+
|
|
299
|
+
**Pattern 1: Static Text**
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
<Text border={true} width={40}>
|
|
303
|
+
This is a simple text box with a border.
|
|
304
|
+
</Text>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Pattern 2: Reactive Text**
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
const playerName = new State('Hero');
|
|
311
|
+
|
|
312
|
+
<Text>Player: {playerName}</Text>;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Pattern 3: Multi-line with Pilcrow**
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
<Text>Line one¶Line two¶Line three</Text>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Pattern 4: Scrollable Content**
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
<Text scrollable={true} height={10} width={60}>
|
|
325
|
+
Long content that needs scrolling...
|
|
326
|
+
</Text>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Pattern 5: Typewriter Effect**
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
<Text typewriter={true} typewriterSpeed={30}>
|
|
333
|
+
This text appears character by character...
|
|
334
|
+
</Text>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Common Mistakes
|
|
338
|
+
|
|
339
|
+
❌ **WRONG** - Using \n for newlines
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
<Text>Line one\nLine two</Text> // \n doesn't work in text content
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
✅ **CORRECT** - Using ¶ (pilcrow) for newlines
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
<Text>Line one¶Line two</Text>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
❌ **WRONG** - Accessing State directly
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
<Text>{count}</Text> // Shows as object
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
✅ **CORRECT** - Both work (State is auto-resolved in Text)
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
<Text>{count}</Text> // State auto-resolved
|
|
361
|
+
<Text>{count.value}</Text> // Explicit .value also works
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Button
|
|
367
|
+
|
|
368
|
+
### Basic Usage
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
// Using JSX children
|
|
372
|
+
<Button onClick={() => console.log('clicked')}>
|
|
373
|
+
Click Me
|
|
374
|
+
</Button>
|
|
375
|
+
|
|
376
|
+
// Using content prop
|
|
377
|
+
<Button content="Click Me" onClick={() => console.log('clicked')} />
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Props
|
|
381
|
+
|
|
382
|
+
| Prop | Type | Default | Description |
|
|
383
|
+
| ---------- | ------------ | -------------------- | --------------------------------------------- |
|
|
384
|
+
| `content` | `string` | - | Button text (alternative to children) |
|
|
385
|
+
| `children` | `string` | - | JSX children |
|
|
386
|
+
| `onClick` | `() => void` | - | Click handler |
|
|
387
|
+
| `width` | `number` | `content.length + 7` | Auto-calculated based on content |
|
|
388
|
+
| `height` | `number` | `4` | Button height |
|
|
389
|
+
| `border` | `boolean` | `true` | Show border |
|
|
390
|
+
| `hotkey` | `string` | - | Hotkey letter (press F1 to toggle visibility) |
|
|
391
|
+
|
|
392
|
+
### Common Patterns
|
|
393
|
+
|
|
394
|
+
**Pattern 1: Simple Button**
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
<Button onClick={() => startGame()}>Start Game</Button>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Pattern 2: State-Modifying Button**
|
|
401
|
+
|
|
402
|
+
```tsx
|
|
403
|
+
const count = new State(0);
|
|
404
|
+
|
|
405
|
+
<Button onClick={() => count.value++}>Count: {count}</Button>;
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Pattern 3: Button with Hotkey**
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
<Button hotkey="s" onClick={() => save()}>
|
|
412
|
+
Save Game
|
|
413
|
+
</Button>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Common Mistakes
|
|
417
|
+
|
|
418
|
+
❌ **WRONG** - Using onClick with event parameter
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
<Button onClick={(e) => handleClick(e)}>Click</Button> // NO event parameter
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
✅ **CORRECT** - onClick receives no parameters
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
<Button onClick={() => handleClick()}>Click</Button>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
❌ **WRONG** - Setting width too small
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
<Button width={5}>Very Long Text</Button> // Text will be cut off
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
✅ **CORRECT** - Let it auto-size or calculate properly
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
<Button>Very Long Text</Button> // Auto-sizes
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Select
|
|
445
|
+
|
|
446
|
+
### Basic Usage
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
import { State, Select, Option } from 'asciitorium';
|
|
450
|
+
|
|
451
|
+
const selected = new State<string>('option1');
|
|
452
|
+
|
|
453
|
+
<Select selected={selected}>
|
|
454
|
+
<Option value="option1" label="First Option" />
|
|
455
|
+
<Option value="option2" label="Second Option" />
|
|
456
|
+
<Option value="option3" label="Third Option" />
|
|
457
|
+
</Select>;
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Props (Select)
|
|
461
|
+
|
|
462
|
+
| Prop | Type | Default | Description |
|
|
463
|
+
| ---------- | ------------- | ------- | -------------------- |
|
|
464
|
+
| `selected` | `State<T>` | - | Selected value state |
|
|
465
|
+
| `children` | `Option<T>[]` | - | Option children |
|
|
466
|
+
| `width` | `number` | - | Component width |
|
|
467
|
+
| `height` | `number` | `3` | Component height |
|
|
468
|
+
| `border` | `boolean` | `true` | Show border |
|
|
469
|
+
|
|
470
|
+
### Props (Option)
|
|
471
|
+
|
|
472
|
+
| Prop | Type | Description |
|
|
473
|
+
| ------- | -------- | ------------- |
|
|
474
|
+
| `value` | `T` | Option value |
|
|
475
|
+
| `label` | `string` | Display label |
|
|
476
|
+
|
|
477
|
+
### Common Patterns
|
|
478
|
+
|
|
479
|
+
**Pattern 1: String Select**
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
const difficulty = new State<string>('normal');
|
|
483
|
+
|
|
484
|
+
<Select selected={difficulty}>
|
|
485
|
+
<Option value="easy" label="Easy Mode" />
|
|
486
|
+
<Option value="normal" label="Normal Mode" />
|
|
487
|
+
<Option value="hard" label="Hard Mode" />
|
|
488
|
+
</Select>;
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Pattern 2: Grouped Options**
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
import { Select, Option, OptionGroup } from 'asciitorium';
|
|
495
|
+
|
|
496
|
+
const weapon = new State<string>('sword');
|
|
497
|
+
|
|
498
|
+
<Select selected={weapon}>
|
|
499
|
+
<OptionGroup label="Melee">
|
|
500
|
+
<Option value="sword" label="Sword" />
|
|
501
|
+
<Option value="axe" label="Axe" />
|
|
502
|
+
</OptionGroup>
|
|
503
|
+
<OptionGroup label="Ranged">
|
|
504
|
+
<Option value="bow" label="Bow" />
|
|
505
|
+
<Option value="crossbow" label="Crossbow" />
|
|
506
|
+
</OptionGroup>
|
|
507
|
+
</Select>;
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Pattern 3: React to Selection**
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
const mode = new State<string>('explore');
|
|
514
|
+
|
|
515
|
+
// Subscribe to changes
|
|
516
|
+
mode.subscribe((newMode) => {
|
|
517
|
+
console.log(`Mode changed to: ${newMode}`);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
<Select selected={mode}>
|
|
521
|
+
<Option value="explore" label="Explore" />
|
|
522
|
+
<Option value="combat" label="Combat" />
|
|
523
|
+
</Select>;
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Common Mistakes
|
|
527
|
+
|
|
528
|
+
❌ **WRONG** - Using value without State
|
|
529
|
+
|
|
530
|
+
```tsx
|
|
531
|
+
<Select selected="option1"> // NO! selected must be a State
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
✅ **CORRECT** - Using State
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
const selected = new State("option1");
|
|
538
|
+
<Select selected={selected}>
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
❌ **WRONG** - Old API (items array)
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
<Select items={['a', 'b']} selectedItem={state} /> // Deprecated
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
✅ **CORRECT** - New API (Option children)
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
<Select selected={state}>
|
|
551
|
+
<Option value="a" label="Option A" />
|
|
552
|
+
<Option value="b" label="Option B" />
|
|
553
|
+
</Select>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Switch
|
|
559
|
+
|
|
560
|
+
### Basic Usage
|
|
561
|
+
|
|
562
|
+
```tsx
|
|
563
|
+
import { State, Switch } from 'asciitorium';
|
|
564
|
+
|
|
565
|
+
// State holding the component to display
|
|
566
|
+
const currentView = new State<any>(DashboardComponent);
|
|
567
|
+
|
|
568
|
+
<Switch component={currentView} />;
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Props
|
|
572
|
+
|
|
573
|
+
| Prop | Type | Default | Description |
|
|
574
|
+
| ----------- | ---------------------------------------------------------------- | ------- | --------------------------------------------------------- |
|
|
575
|
+
| `component` | `State<Component \| (() => Component) \| (new () => Component)>` | - | State holding component instance, constructor, or factory |
|
|
576
|
+
| `width` | `number \| 'fill'` | - | Component width |
|
|
577
|
+
| `height` | `number \| 'fill'` | - | Component height |
|
|
578
|
+
|
|
579
|
+
### Common Patterns
|
|
580
|
+
|
|
581
|
+
**Pattern 1: View Switching**
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
import { State, Switch, Button, Column } from 'asciitorium';
|
|
585
|
+
|
|
586
|
+
// Component classes/constructors
|
|
587
|
+
class DashboardView extends Component {
|
|
588
|
+
/* ... */
|
|
589
|
+
}
|
|
590
|
+
class SettingsView extends Component {
|
|
591
|
+
/* ... */
|
|
592
|
+
}
|
|
593
|
+
class ProfileView extends Component {
|
|
594
|
+
/* ... */
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const currentView = new State<any>(DashboardView);
|
|
598
|
+
|
|
599
|
+
<Column>
|
|
600
|
+
<Button onClick={() => (currentView.value = DashboardView)}>Dashboard</Button>
|
|
601
|
+
<Button onClick={() => (currentView.value = SettingsView)}>Settings</Button>
|
|
602
|
+
<Button onClick={() => (currentView.value = ProfileView)}>Profile</Button>
|
|
603
|
+
|
|
604
|
+
<Switch component={currentView} width="fill" height="fill" />
|
|
605
|
+
</Column>;
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Pattern 2: Dynamic Content with Select**
|
|
609
|
+
|
|
610
|
+
```tsx
|
|
611
|
+
import { State, Select, Option, Switch } from 'asciitorium';
|
|
612
|
+
|
|
613
|
+
// Component map
|
|
614
|
+
const pageMap: Record<string, any> = {
|
|
615
|
+
home: HomeComponent,
|
|
616
|
+
about: AboutComponent,
|
|
617
|
+
contact: ContactComponent,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const selectedKey = new State<string>('home');
|
|
621
|
+
const selectedComponent = new State<any>(pageMap['home']);
|
|
622
|
+
|
|
623
|
+
// Sync selected component with key
|
|
624
|
+
selectedKey.subscribe((key) => {
|
|
625
|
+
selectedComponent.value = pageMap[key];
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
<Column>
|
|
629
|
+
<Select selected={selectedKey}>
|
|
630
|
+
<Option value="home" label="Home" />
|
|
631
|
+
<Option value="about" label="About" />
|
|
632
|
+
<Option value="contact" label="Contact" />
|
|
633
|
+
</Select>
|
|
634
|
+
|
|
635
|
+
<Switch component={selectedComponent} width="fill" height="fill" />
|
|
636
|
+
</Column>;
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Pattern 3: Factory Functions**
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
// Using factory functions instead of constructors
|
|
643
|
+
const viewFactory = () => new CustomView({ customProp: 'value' });
|
|
644
|
+
const currentView = new State<any>(viewFactory);
|
|
645
|
+
|
|
646
|
+
<Switch component={currentView} />;
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**Pattern 4: Component Instances**
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
// Using pre-instantiated components
|
|
653
|
+
const dashboard = new DashboardView({ width: 80 });
|
|
654
|
+
const settings = new SettingsView({ width: 80 });
|
|
655
|
+
|
|
656
|
+
const currentView = new State<Component>(dashboard);
|
|
657
|
+
|
|
658
|
+
<Button onClick={() => currentView.value = settings}>Switch to Settings</Button>
|
|
659
|
+
<Switch component={currentView} />
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Common Mistakes
|
|
663
|
+
|
|
664
|
+
❌ **WRONG** - Not using State
|
|
665
|
+
|
|
666
|
+
```tsx
|
|
667
|
+
<Switch component={MyComponent} /> // NO! component must be a State
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
✅ **CORRECT** - Using State
|
|
671
|
+
|
|
672
|
+
```tsx
|
|
673
|
+
const view = new State(MyComponent);
|
|
674
|
+
<Switch component={view} />;
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
❌ **WRONG** - Trying to pass component as string
|
|
678
|
+
|
|
679
|
+
```tsx
|
|
680
|
+
const view = new State<string>('DashboardComponent'); // NO! Must be actual component
|
|
681
|
+
<Switch component={view} />;
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
✅ **CORRECT** - Using actual component reference
|
|
685
|
+
|
|
686
|
+
```tsx
|
|
687
|
+
const view = new State<any>(DashboardComponent); // Component constructor
|
|
688
|
+
<Switch component={view} />;
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## TextInput
|
|
694
|
+
|
|
695
|
+
### Basic Usage
|
|
696
|
+
|
|
697
|
+
```tsx
|
|
698
|
+
const username = new State('');
|
|
699
|
+
|
|
700
|
+
<TextInput value={username} placeholder="Enter username..." />;
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Props
|
|
704
|
+
|
|
705
|
+
| Prop | Type | Default | Description |
|
|
706
|
+
| ------------- | -------------------------------- | ------- | ------------------------------------------ |
|
|
707
|
+
| `value` | `State<string> \| State<number>` | - | Input value state (string or number) |
|
|
708
|
+
| `placeholder` | `string` | `''` | Placeholder text |
|
|
709
|
+
| `numeric` | `boolean` | `false` | Restricts input to numbers only |
|
|
710
|
+
| `width` | `number` | - | Input width (often needs explicit setting) |
|
|
711
|
+
| `height` | `number` | `3` | Input height |
|
|
712
|
+
| `border` | `boolean` | `true` | Show border |
|
|
713
|
+
| `maxLength` | `number` | - | Maximum input length |
|
|
714
|
+
|
|
715
|
+
### Common Patterns
|
|
716
|
+
|
|
717
|
+
**Pattern 1: Simple Input**
|
|
718
|
+
|
|
719
|
+
```tsx
|
|
720
|
+
const name = new State('');
|
|
721
|
+
|
|
722
|
+
<TextInput value={name} placeholder="Your name" width={30} />;
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
**Pattern 2: Form with Validation**
|
|
726
|
+
|
|
727
|
+
```tsx
|
|
728
|
+
const email = new State('');
|
|
729
|
+
|
|
730
|
+
email.subscribe((value) => {
|
|
731
|
+
if (value.includes('@')) {
|
|
732
|
+
console.log('Valid email');
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
<TextInput value={email} placeholder="email@example.com" />;
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
**Pattern 3: Max Length**
|
|
740
|
+
|
|
741
|
+
```tsx
|
|
742
|
+
const code = new State('');
|
|
743
|
+
|
|
744
|
+
<TextInput value={code} maxLength={6} placeholder="Enter code" />;
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### Common Mistakes
|
|
748
|
+
|
|
749
|
+
❌ **WRONG** - Using string directly
|
|
750
|
+
|
|
751
|
+
```tsx
|
|
752
|
+
<TextInput value="static text" /> // NO! value must be State
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
✅ **CORRECT** - Using State
|
|
756
|
+
|
|
757
|
+
```tsx
|
|
758
|
+
const text = new State('initial');
|
|
759
|
+
<TextInput value={text} />;
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
❌ **WRONG** - Trying to get value incorrectly
|
|
763
|
+
|
|
764
|
+
```tsx
|
|
765
|
+
<Button onClick={() => console.log(inputValue)}>Submit</Button>
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
✅ **CORRECT** - Accessing .value
|
|
769
|
+
|
|
770
|
+
```tsx
|
|
771
|
+
<Button onClick={() => console.log(inputValue.value)}>Submit</Button>
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## Art
|
|
777
|
+
|
|
778
|
+
### Basic Usage
|
|
779
|
+
|
|
780
|
+
```tsx
|
|
781
|
+
// Load from file
|
|
782
|
+
<Art src="logo" />
|
|
783
|
+
|
|
784
|
+
// ASCII art from figlet font
|
|
785
|
+
<Art font="standard" text="Hello" />
|
|
786
|
+
|
|
787
|
+
// Align center
|
|
788
|
+
<Art src="banner" align="center" />
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Props
|
|
792
|
+
|
|
793
|
+
| Prop | Type | Default | Description |
|
|
794
|
+
| -------- | ------------------------------- | -------- | -------------------------------- |
|
|
795
|
+
| `src` | `string` | - | Art file name (from art/ folder) |
|
|
796
|
+
| `font` | `string` | - | Figlet font name |
|
|
797
|
+
| `text` | `string` | - | Text to render with font |
|
|
798
|
+
| `width` | `number \| 'auto'` | `'auto'` | Component width |
|
|
799
|
+
| `height` | `number \| 'auto'` | `'auto'` | Component height |
|
|
800
|
+
| `align` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment |
|
|
801
|
+
| `border` | `boolean` | `false` | Show border |
|
|
802
|
+
|
|
803
|
+
### Common Patterns
|
|
804
|
+
|
|
805
|
+
**Pattern 1: Logo from File**
|
|
806
|
+
|
|
807
|
+
```tsx
|
|
808
|
+
// Loads from art/logo.art
|
|
809
|
+
<Art src="logo" align="center" />
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
**Pattern 2: Generated ASCII Text**
|
|
813
|
+
|
|
814
|
+
```tsx
|
|
815
|
+
<Art font="standard" text="Game Over" align="center" />
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
**Pattern 3: Animated Sprite**
|
|
819
|
+
|
|
820
|
+
```tsx
|
|
821
|
+
// Loads animated art (auto-plays)
|
|
822
|
+
<Art src="beating-heart" width={20} />
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### Common Mistakes
|
|
826
|
+
|
|
827
|
+
❌ **WRONG** - Using both src and font
|
|
828
|
+
|
|
829
|
+
```tsx
|
|
830
|
+
<Art src="logo" font="standard" /> // Conflicting props
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
✅ **CORRECT** - Use one or the other
|
|
834
|
+
|
|
835
|
+
```tsx
|
|
836
|
+
<Art src="logo" /> // From file
|
|
837
|
+
<Art font="standard" text="Title" /> // Generated
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
## Keybind
|
|
843
|
+
|
|
844
|
+
### Basic Usage
|
|
845
|
+
|
|
846
|
+
```tsx
|
|
847
|
+
<Keybind keyBinding="F12" action={() => console.log('F12 pressed')} />
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### Props
|
|
851
|
+
|
|
852
|
+
| Prop | Type | Default | Description |
|
|
853
|
+
| ------------- | --------------------------- | ------- | -------------------------------------------- |
|
|
854
|
+
| `keyBinding` | `string` | - | Key combination (e.g., "F12", "Ctrl+s", "p") |
|
|
855
|
+
| `action` | `() => void` | - | Function to execute |
|
|
856
|
+
| `description` | `string` | - | Optional description |
|
|
857
|
+
| `disabled` | `State<boolean> \| boolean` | `false` | Disable keybind |
|
|
858
|
+
| `priority` | `number` | `0` | Priority for conflict resolution |
|
|
859
|
+
|
|
860
|
+
### Common Patterns
|
|
861
|
+
|
|
862
|
+
**Pattern 1: Toggle Visibility**
|
|
863
|
+
|
|
864
|
+
```tsx
|
|
865
|
+
const showDebug = new State(false);
|
|
866
|
+
|
|
867
|
+
<Keybind keyBinding="d" action={() => (showDebug.value = !showDebug.value)} />;
|
|
868
|
+
|
|
869
|
+
{
|
|
870
|
+
showDebug.value && <Text>Debug Info</Text>;
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Pattern 2: Game Controls**
|
|
875
|
+
|
|
876
|
+
```tsx
|
|
877
|
+
<Keybind keyBinding="ArrowUp" action={() => gameWorld.moveForward()} />
|
|
878
|
+
<Keybind keyBinding="ArrowDown" action={() => gameWorld.moveBackward()} />
|
|
879
|
+
<Keybind keyBinding="ArrowLeft" action={() => gameWorld.turnLeft()} />
|
|
880
|
+
<Keybind keyBinding="ArrowRight" action={() => gameWorld.turnRight()} />
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
**Pattern 3: Conditional Keybind**
|
|
884
|
+
|
|
885
|
+
```tsx
|
|
886
|
+
const gameStarted = new State(false);
|
|
887
|
+
|
|
888
|
+
<Keybind
|
|
889
|
+
keyBinding="Space"
|
|
890
|
+
action={() => shoot()}
|
|
891
|
+
disabled={gameStarted.not()}
|
|
892
|
+
/>;
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
**Pattern 4: Function Keys**
|
|
896
|
+
|
|
897
|
+
```tsx
|
|
898
|
+
<Keybind keyBinding="F1" action={() => showHelp()} description="Show help" />
|
|
899
|
+
<Keybind keyBinding="F12" action={() => toggleDebug()} description="Toggle debug" />
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### Common Mistakes
|
|
903
|
+
|
|
904
|
+
❌ **WRONG** - Using event object
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
<Keybind action={(e) => console.log(e.key)} /> // NO event parameter
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
✅ **CORRECT** - Action receives no parameters
|
|
911
|
+
|
|
912
|
+
```tsx
|
|
913
|
+
<Keybind keyBinding="p" action={() => console.log('p pressed')} />
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
❌ **WRONG** - Assuming global scope without checking focused component
|
|
917
|
+
|
|
918
|
+
```tsx
|
|
919
|
+
// If a TextInput has focus, letter keys go to the input, not the keybind
|
|
920
|
+
<Keybind keyBinding="w" action={() => moveUp()} />
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
✅ **CORRECT** - Use non-conflicting keys for global actions
|
|
924
|
+
|
|
925
|
+
```tsx
|
|
926
|
+
<Keybind keyBinding="ArrowUp" action={() => moveUp()} />
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
---
|
|
930
|
+
|
|
931
|
+
## GameWorld
|
|
932
|
+
|
|
933
|
+
### Basic Usage
|
|
934
|
+
|
|
935
|
+
```tsx
|
|
936
|
+
import { GameWorld, State } from 'asciitorium';
|
|
937
|
+
|
|
938
|
+
const gameWorld = new GameWorld({
|
|
939
|
+
mapName: 'dungeon',
|
|
940
|
+
initialPosition: { x: 5, y: 5, direction: 'north' },
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
// Wait for async load
|
|
944
|
+
await gameWorld.ready;
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### Constructor Options
|
|
948
|
+
|
|
949
|
+
| Option | Type | Description |
|
|
950
|
+
| ----------------- | ------------------------------------------------ | -------------------------------------- |
|
|
951
|
+
| `mapName` | `string` | Map name (loads from art/maps/{name}/) |
|
|
952
|
+
| `initialPosition` | `{ x: number, y: number, direction: Direction }` | Starting player position |
|
|
953
|
+
|
|
954
|
+
### Methods
|
|
955
|
+
|
|
956
|
+
| Method | Description |
|
|
957
|
+
| ---------------- | ----------------------------------------- |
|
|
958
|
+
| `moveForward()` | Move player forward (respects collision) |
|
|
959
|
+
| `moveBackward()` | Move player backward (respects collision) |
|
|
960
|
+
| `turnLeft()` | Turn player left (no movement) |
|
|
961
|
+
| `turnRight()` | Turn player right (no movement) |
|
|
962
|
+
| `isSolid(x, y)` | Check if tile is solid (legend-based) |
|
|
963
|
+
| `getMapData()` | Get map string array |
|
|
964
|
+
| `getPlayer()` | Get current player state |
|
|
965
|
+
| `getLegend()` | Get legend metadata |
|
|
966
|
+
| `isReady()` | Check if assets are loaded |
|
|
967
|
+
|
|
968
|
+
### Common Patterns
|
|
969
|
+
|
|
970
|
+
**Pattern 1: Basic Game Setup**
|
|
971
|
+
|
|
972
|
+
```tsx
|
|
973
|
+
const gameWorld = new GameWorld({
|
|
974
|
+
mapName: 'castle',
|
|
975
|
+
initialPosition: { x: 10, y: 10, direction: 'north' },
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
await gameWorld.ready;
|
|
979
|
+
|
|
980
|
+
<App>
|
|
981
|
+
<Keybind keyBinding="ArrowUp" action={() => gameWorld.moveForward()} />
|
|
982
|
+
<Keybind keyBinding="ArrowDown" action={() => gameWorld.moveBackward()} />
|
|
983
|
+
<Keybind keyBinding="ArrowLeft" action={() => gameWorld.turnLeft()} />
|
|
984
|
+
<Keybind keyBinding="ArrowRight" action={() => gameWorld.turnRight()} />
|
|
985
|
+
|
|
986
|
+
<MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
987
|
+
<FirstPersonView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
988
|
+
</App>;
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**Pattern 2: Custom Movement Logic**
|
|
992
|
+
|
|
993
|
+
```tsx
|
|
994
|
+
const handleMove = () => {
|
|
995
|
+
const player = gameWorld.getPlayer();
|
|
996
|
+
const nextPos = calculateNextPosition(player);
|
|
997
|
+
|
|
998
|
+
if (!gameWorld.isSolid(nextPos.x, nextPos.y)) {
|
|
999
|
+
gameWorld.moveForward();
|
|
1000
|
+
} else {
|
|
1001
|
+
console.log('Blocked!');
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
<Keybind keyBinding="w" action={handleMove} />;
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
**Pattern 3: Fog of War**
|
|
1009
|
+
|
|
1010
|
+
```tsx
|
|
1011
|
+
const exploredTiles = new State(new Set<string>());
|
|
1012
|
+
|
|
1013
|
+
gameWorld.player.subscribe((player) => {
|
|
1014
|
+
const key = `${player.x},${player.y}`;
|
|
1015
|
+
const newSet = new Set(exploredTiles.value);
|
|
1016
|
+
newSet.add(key);
|
|
1017
|
+
exploredTiles.value = newSet;
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
<MapView
|
|
1021
|
+
mapAsset={gameWorld.mapAsset}
|
|
1022
|
+
player={gameWorld.player}
|
|
1023
|
+
fogOfWar={true}
|
|
1024
|
+
exploredTiles={exploredTiles}
|
|
1025
|
+
/>;
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### Common Mistakes
|
|
1029
|
+
|
|
1030
|
+
❌ **WRONG** - Not waiting for ready
|
|
1031
|
+
|
|
1032
|
+
```tsx
|
|
1033
|
+
const gameWorld = new GameWorld({ mapName: 'dungeon', ... });
|
|
1034
|
+
// Assets not loaded yet!
|
|
1035
|
+
<MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
✅ **CORRECT** - Await ready
|
|
1039
|
+
|
|
1040
|
+
```tsx
|
|
1041
|
+
const gameWorld = new GameWorld({ mapName: 'dungeon', ... });
|
|
1042
|
+
await gameWorld.ready;
|
|
1043
|
+
<MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
❌ **WRONG** - Modifying player state directly
|
|
1047
|
+
|
|
1048
|
+
```tsx
|
|
1049
|
+
gameWorld.player.value.x += 1; // Bypasses collision detection
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
✅ **CORRECT** - Using movement methods
|
|
1053
|
+
|
|
1054
|
+
```tsx
|
|
1055
|
+
gameWorld.moveForward(); // Handles collision
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
---
|
|
1059
|
+
|
|
1060
|
+
## MapView
|
|
1061
|
+
|
|
1062
|
+
### Basic Usage
|
|
1063
|
+
|
|
1064
|
+
```tsx
|
|
1065
|
+
<MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
### Props
|
|
1069
|
+
|
|
1070
|
+
| Prop | Type | Default | Description |
|
|
1071
|
+
| --------------- | ----------------------------------- | -------- | ------------------------------ |
|
|
1072
|
+
| `mapAsset` | `State<MapAsset \| null>` | - | Map asset state |
|
|
1073
|
+
| `player` | `State<Player>` | - | Player state |
|
|
1074
|
+
| `fogOfWar` | `boolean \| State<boolean>` | `false` | Enable fog of war |
|
|
1075
|
+
| `exploredTiles` | `Set<string> \| State<Set<string>>` | - | Explored tiles (for fog) |
|
|
1076
|
+
| `fogCharacter` | `string` | `' '` | Character for unexplored tiles |
|
|
1077
|
+
| `width` | `number \| 'fill'` | `'fill'` | Component width |
|
|
1078
|
+
| `height` | `number \| 'fill'` | `'fill'` | Component height |
|
|
1079
|
+
| `border` | `boolean` | `true` | Show border |
|
|
1080
|
+
|
|
1081
|
+
### Common Patterns
|
|
1082
|
+
|
|
1083
|
+
**Pattern 1: Simple Map Display**
|
|
1084
|
+
|
|
1085
|
+
```tsx
|
|
1086
|
+
<MapView
|
|
1087
|
+
mapAsset={gameWorld.mapAsset}
|
|
1088
|
+
player={gameWorld.player}
|
|
1089
|
+
width={60}
|
|
1090
|
+
height={20}
|
|
1091
|
+
/>
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
**Pattern 2: Map with Fog of War**
|
|
1095
|
+
|
|
1096
|
+
```tsx
|
|
1097
|
+
const exploredTiles = new State(new Set<string>());
|
|
1098
|
+
|
|
1099
|
+
// Update explored tiles when player moves
|
|
1100
|
+
gameWorld.player.subscribe((p) => {
|
|
1101
|
+
const newSet = new Set(exploredTiles.value);
|
|
1102
|
+
newSet.add(`${p.x},${p.y}`);
|
|
1103
|
+
exploredTiles.value = newSet;
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
<MapView
|
|
1107
|
+
mapAsset={gameWorld.mapAsset}
|
|
1108
|
+
player={gameWorld.player}
|
|
1109
|
+
fogOfWar={true}
|
|
1110
|
+
exploredTiles={exploredTiles}
|
|
1111
|
+
fogCharacter="░"
|
|
1112
|
+
/>;
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
**Pattern 3: Minimap**
|
|
1116
|
+
|
|
1117
|
+
```tsx
|
|
1118
|
+
<MapView
|
|
1119
|
+
mapAsset={gameWorld.mapAsset}
|
|
1120
|
+
player={gameWorld.player}
|
|
1121
|
+
width={30}
|
|
1122
|
+
height={15}
|
|
1123
|
+
border={true}
|
|
1124
|
+
/>
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
### Common Mistakes
|
|
1128
|
+
|
|
1129
|
+
❌ **WRONG** - Using raw map data
|
|
1130
|
+
|
|
1131
|
+
```tsx
|
|
1132
|
+
<MapView mapData={['###', '#.#', '###']} /> // Old API
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
✅ **CORRECT** - Using GameWorld's mapAsset and player
|
|
1136
|
+
|
|
1137
|
+
```tsx
|
|
1138
|
+
<MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
---
|
|
1142
|
+
|
|
1143
|
+
## FirstPersonView
|
|
1144
|
+
|
|
1145
|
+
### Basic Usage
|
|
1146
|
+
|
|
1147
|
+
```tsx
|
|
1148
|
+
<FirstPersonView
|
|
1149
|
+
mapAsset={gameWorld.mapAsset}
|
|
1150
|
+
player={gameWorld.player}
|
|
1151
|
+
scene="brick-wall"
|
|
1152
|
+
/>
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
### Props
|
|
1156
|
+
|
|
1157
|
+
| Prop | Type | Default | Description |
|
|
1158
|
+
| ---------- | ------------------------- | ------------- | ---------------- |
|
|
1159
|
+
| `mapAsset` | `State<MapAsset \| null>` | - | Map asset state |
|
|
1160
|
+
| `player` | `State<Player>` | - | Player state |
|
|
1161
|
+
| `scene` | `string` | `'wireframe'` | Scene style name |
|
|
1162
|
+
| `width` | `number \| 'fill'` | `'fill'` | Component width |
|
|
1163
|
+
| `height` | `number \| 'fill'` | `'fill'` | Component height |
|
|
1164
|
+
| `border` | `boolean` | `true` | Show border |
|
|
1165
|
+
|
|
1166
|
+
### Available Scenes
|
|
1167
|
+
|
|
1168
|
+
- `wireframe` - Simple line-based rendering
|
|
1169
|
+
- `brick-wall` - Textured brick walls
|
|
1170
|
+
- `stone-wall` - Stone texture
|
|
1171
|
+
- `dungeon` - Dark dungeon atmosphere
|
|
1172
|
+
|
|
1173
|
+
### Common Patterns
|
|
1174
|
+
|
|
1175
|
+
**Pattern 1: Basic First-Person View**
|
|
1176
|
+
|
|
1177
|
+
```tsx
|
|
1178
|
+
<FirstPersonView
|
|
1179
|
+
mapAsset={gameWorld.mapAsset}
|
|
1180
|
+
player={gameWorld.player}
|
|
1181
|
+
scene="brick-wall"
|
|
1182
|
+
width={80}
|
|
1183
|
+
height={40}
|
|
1184
|
+
/>
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
**Pattern 2: Split View (Map + First Person)**
|
|
1188
|
+
|
|
1189
|
+
```tsx
|
|
1190
|
+
<Column width="100%" height="100%">
|
|
1191
|
+
<FirstPersonView
|
|
1192
|
+
mapAsset={gameWorld.mapAsset}
|
|
1193
|
+
player={gameWorld.player}
|
|
1194
|
+
scene="dungeon"
|
|
1195
|
+
height={30}
|
|
1196
|
+
/>
|
|
1197
|
+
<MapView
|
|
1198
|
+
mapAsset={gameWorld.mapAsset}
|
|
1199
|
+
player={gameWorld.player}
|
|
1200
|
+
height={15}
|
|
1201
|
+
/>
|
|
1202
|
+
</Column>
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
### Common Mistakes
|
|
1206
|
+
|
|
1207
|
+
❌ **WRONG** - Using legacy src prop
|
|
1208
|
+
|
|
1209
|
+
```tsx
|
|
1210
|
+
<FirstPersonView src={mapData} player={player} /> // Old API
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
✅ **CORRECT** - Using mapAsset
|
|
1214
|
+
|
|
1215
|
+
```tsx
|
|
1216
|
+
<FirstPersonView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
---
|
|
1220
|
+
|
|
1221
|
+
## PerfMonitor
|
|
1222
|
+
|
|
1223
|
+
### Basic Usage
|
|
1224
|
+
|
|
1225
|
+
```tsx
|
|
1226
|
+
const showPerf = new State(false);
|
|
1227
|
+
|
|
1228
|
+
<Keybind keyBinding="p" action={() => showPerf.value = !showPerf.value} />
|
|
1229
|
+
<PerfMonitor visible={showPerf} />
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
### Props
|
|
1233
|
+
|
|
1234
|
+
| Prop | Type | Default | Description |
|
|
1235
|
+
| ---------- | --------------------------- | ---------------- | ----------------- |
|
|
1236
|
+
| `visible` | `State<boolean> \| boolean` | `true` | Show/hide monitor |
|
|
1237
|
+
| `position` | `{ x: number, y: number }` | `{ x: 0, y: 0 }` | Screen position |
|
|
1238
|
+
|
|
1239
|
+
### Common Patterns
|
|
1240
|
+
|
|
1241
|
+
**Pattern 1: Toggle with Keybind**
|
|
1242
|
+
|
|
1243
|
+
```tsx
|
|
1244
|
+
const showPerf = new State(false);
|
|
1245
|
+
|
|
1246
|
+
<Keybind keyBinding="F3" action={() => showPerf.value = !showPerf.value} />
|
|
1247
|
+
<PerfMonitor visible={showPerf} />
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
**Pattern 2: Always Visible (Dev Mode)**
|
|
1251
|
+
|
|
1252
|
+
```tsx
|
|
1253
|
+
<PerfMonitor visible={true} />
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
### Common Mistakes
|
|
1257
|
+
|
|
1258
|
+
❌ **WRONG** - Expecting control over position
|
|
1259
|
+
|
|
1260
|
+
```tsx
|
|
1261
|
+
<PerfMonitor position={{ x: 100, y: 50 }} /> // Position prop exists but fixed to corner
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
✅ **CORRECT** - Accept default positioning
|
|
1265
|
+
|
|
1266
|
+
```tsx
|
|
1267
|
+
<PerfMonitor visible={showPerf} />
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
---
|
|
1271
|
+
|
|
1272
|
+
## Component Lifecycle
|
|
1273
|
+
|
|
1274
|
+
### Initialization
|
|
1275
|
+
|
|
1276
|
+
```tsx
|
|
1277
|
+
// Components are instantiated when JSX is executed
|
|
1278
|
+
const app = (
|
|
1279
|
+
<App>
|
|
1280
|
+
<Text>This creates a new Text instance</Text>
|
|
1281
|
+
</App>
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
// App starts rendering
|
|
1285
|
+
await app.start();
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
### State Subscriptions
|
|
1289
|
+
|
|
1290
|
+
```tsx
|
|
1291
|
+
const count = new State(0);
|
|
1292
|
+
|
|
1293
|
+
// Manual subscription
|
|
1294
|
+
count.subscribe((newValue) => {
|
|
1295
|
+
console.log('Count changed:', newValue);
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
// Component auto-subscribes when State is passed as prop
|
|
1299
|
+
<Text>{count}</Text>; // Auto re-renders when count changes
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
### Cleanup
|
|
1303
|
+
|
|
1304
|
+
```tsx
|
|
1305
|
+
// Components auto-cleanup when destroyed
|
|
1306
|
+
// State subscriptions are automatically removed
|
|
1307
|
+
// No manual cleanup needed in most cases
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
## Legend System (Game Maps)
|
|
1313
|
+
|
|
1314
|
+
### Legend File Format
|
|
1315
|
+
|
|
1316
|
+
Each map has a `legend.json` file that describes tile properties:
|
|
1317
|
+
|
|
1318
|
+
```json
|
|
1319
|
+
{
|
|
1320
|
+
"#": {
|
|
1321
|
+
"name": "Wall",
|
|
1322
|
+
"kind": "material",
|
|
1323
|
+
"solid": true,
|
|
1324
|
+
"asset": "brick-wall"
|
|
1325
|
+
},
|
|
1326
|
+
".": {
|
|
1327
|
+
"name": "Floor",
|
|
1328
|
+
"kind": "material",
|
|
1329
|
+
"solid": false,
|
|
1330
|
+
"asset": "floor"
|
|
1331
|
+
},
|
|
1332
|
+
"D": {
|
|
1333
|
+
"name": "Door",
|
|
1334
|
+
"kind": "sprite",
|
|
1335
|
+
"solid": true,
|
|
1336
|
+
"tag": "door",
|
|
1337
|
+
"asset": "door-closed"
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
```
|
|
1341
|
+
|
|
1342
|
+
### Legend Properties
|
|
1343
|
+
|
|
1344
|
+
| Property | Type | Description |
|
|
1345
|
+
| -------- | ------------------------ | --------------------- |
|
|
1346
|
+
| `name` | `string` | Human-readable name |
|
|
1347
|
+
| `kind` | `'material' \| 'sprite'` | Asset type |
|
|
1348
|
+
| `solid` | `boolean` | Blocks movement |
|
|
1349
|
+
| `asset` | `string` | Asset file name |
|
|
1350
|
+
| `tag` | `string` | Optional semantic tag |
|
|
1351
|
+
|
|
1352
|
+
### Common Patterns
|
|
1353
|
+
|
|
1354
|
+
**Pattern 1: Check Collision**
|
|
1355
|
+
|
|
1356
|
+
```tsx
|
|
1357
|
+
const canMove = !gameWorld.isSolid(targetX, targetY);
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
**Pattern 2: Custom Interaction**
|
|
1361
|
+
|
|
1362
|
+
```tsx
|
|
1363
|
+
const tile = gameWorld.getLegend()[tileChar];
|
|
1364
|
+
if (tile?.tag === 'door') {
|
|
1365
|
+
openDoor();
|
|
1366
|
+
} else if (tile?.tag === 'treasure') {
|
|
1367
|
+
collectTreasure();
|
|
1368
|
+
}
|
|
1369
|
+
```
|
|
1370
|
+
|
|
1371
|
+
---
|
|
1372
|
+
|
|
1373
|
+
## Directory Structure
|
|
1374
|
+
|
|
1375
|
+
```
|
|
1376
|
+
your-project/
|
|
1377
|
+
├── src/
|
|
1378
|
+
│ ├── main.tsx # Entry point
|
|
1379
|
+
│ └── ...
|
|
1380
|
+
├── art/
|
|
1381
|
+
│ ├── maps/
|
|
1382
|
+
│ │ └── your-map/
|
|
1383
|
+
│ │ ├── map.art
|
|
1384
|
+
│ │ └── legend.json
|
|
1385
|
+
│ ├── materials/
|
|
1386
|
+
│ │ └── wall.art
|
|
1387
|
+
│ ├── sprites/
|
|
1388
|
+
│ │ └── player.art
|
|
1389
|
+
│ └── your-logo.art
|
|
1390
|
+
├── index.html
|
|
1391
|
+
└── package.json
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
### Art File Format
|
|
1395
|
+
|
|
1396
|
+
Art files use special separators:
|
|
1397
|
+
|
|
1398
|
+
- `§` - Metadata separator (e.g., `§width=80§height=40§`)
|
|
1399
|
+
- `¶` - Line separator in content
|
|
1400
|
+
|
|
1401
|
+
Example:
|
|
1402
|
+
|
|
1403
|
+
```
|
|
1404
|
+
§width=20§height=5§
|
|
1405
|
+
╔══════════════════╗¶
|
|
1406
|
+
║ GAME TITLE ║¶
|
|
1407
|
+
║ ║¶
|
|
1408
|
+
║ Press START ║¶
|
|
1409
|
+
╚══════════════════╝
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
---
|
|
1413
|
+
|
|
1414
|
+
## Type Annotations
|
|
1415
|
+
|
|
1416
|
+
### Player Type
|
|
1417
|
+
|
|
1418
|
+
```tsx
|
|
1419
|
+
import type { Player } from 'asciitorium';
|
|
1420
|
+
|
|
1421
|
+
const player: Player = {
|
|
1422
|
+
x: 5,
|
|
1423
|
+
y: 10,
|
|
1424
|
+
direction: 'north', // 'north' | 'south' | 'east' | 'west'
|
|
1425
|
+
};
|
|
1426
|
+
```
|
|
1427
|
+
|
|
1428
|
+
### MapAsset Type
|
|
1429
|
+
|
|
1430
|
+
```tsx
|
|
1431
|
+
import type { MapAsset } from 'asciitorium';
|
|
1432
|
+
|
|
1433
|
+
// Returned by AssetManager.loadMap()
|
|
1434
|
+
const mapAsset: MapAsset = {
|
|
1435
|
+
mapData: string[];
|
|
1436
|
+
legend: Record<string, LegendEntry>;
|
|
1437
|
+
};
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
### LegendEntry Type
|
|
1441
|
+
|
|
1442
|
+
```tsx
|
|
1443
|
+
import type { LegendEntry } from 'asciitorium';
|
|
1444
|
+
|
|
1445
|
+
const entry: LegendEntry = {
|
|
1446
|
+
name: string;
|
|
1447
|
+
kind: 'material' | 'sprite';
|
|
1448
|
+
solid: boolean;
|
|
1449
|
+
asset: string;
|
|
1450
|
+
tag?: string;
|
|
1451
|
+
};
|
|
1452
|
+
```
|
|
1453
|
+
|
|
1454
|
+
---
|
|
1455
|
+
|
|
1456
|
+
## Complete Example: Simple Game
|
|
1457
|
+
|
|
1458
|
+
```tsx
|
|
1459
|
+
import {
|
|
1460
|
+
App,
|
|
1461
|
+
GameWorld,
|
|
1462
|
+
MapView,
|
|
1463
|
+
FirstPersonView,
|
|
1464
|
+
Keybind,
|
|
1465
|
+
Text,
|
|
1466
|
+
Column,
|
|
1467
|
+
State,
|
|
1468
|
+
} from 'asciitorium';
|
|
1469
|
+
|
|
1470
|
+
// Create game world
|
|
1471
|
+
const gameWorld = new GameWorld({
|
|
1472
|
+
mapName: 'dungeon',
|
|
1473
|
+
initialPosition: { x: 5, y: 5, direction: 'north' },
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
// Wait for assets to load
|
|
1477
|
+
await gameWorld.ready;
|
|
1478
|
+
|
|
1479
|
+
// Fog of war state
|
|
1480
|
+
const exploredTiles = new State(new Set<string>());
|
|
1481
|
+
|
|
1482
|
+
// Update explored tiles on player move
|
|
1483
|
+
gameWorld.player.subscribe((p) => {
|
|
1484
|
+
const newSet = new Set(exploredTiles.value);
|
|
1485
|
+
newSet.add(`${p.x},${p.y}`);
|
|
1486
|
+
exploredTiles.value = newSet;
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
const app = (
|
|
1490
|
+
<App>
|
|
1491
|
+
{/* Movement controls */}
|
|
1492
|
+
<Keybind keyBinding="ArrowUp" action={() => gameWorld.moveForward()} />
|
|
1493
|
+
<Keybind keyBinding="ArrowDown" action={() => gameWorld.moveBackward()} />
|
|
1494
|
+
<Keybind keyBinding="ArrowLeft" action={() => gameWorld.turnLeft()} />
|
|
1495
|
+
<Keybind keyBinding="ArrowRight" action={() => gameWorld.turnRight()} />
|
|
1496
|
+
|
|
1497
|
+
{/* UI Layout */}
|
|
1498
|
+
<Column width="100%" height="100%" gap={0}>
|
|
1499
|
+
{/* First-person view */}
|
|
1500
|
+
<FirstPersonView
|
|
1501
|
+
mapAsset={gameWorld.mapAsset}
|
|
1502
|
+
player={gameWorld.player}
|
|
1503
|
+
scene="dungeon"
|
|
1504
|
+
height={30}
|
|
1505
|
+
/>
|
|
1506
|
+
|
|
1507
|
+
{/* Map view with fog */}
|
|
1508
|
+
<MapView
|
|
1509
|
+
mapAsset={gameWorld.mapAsset}
|
|
1510
|
+
player={gameWorld.player}
|
|
1511
|
+
fogOfWar={true}
|
|
1512
|
+
exploredTiles={exploredTiles}
|
|
1513
|
+
height={15}
|
|
1514
|
+
/>
|
|
1515
|
+
|
|
1516
|
+
{/* Status text */}
|
|
1517
|
+
<Text border={true}>Use arrow keys to move. Explore the dungeon!</Text>
|
|
1518
|
+
</Column>
|
|
1519
|
+
</App>
|
|
1520
|
+
);
|
|
1521
|
+
|
|
1522
|
+
await app.start();
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
---
|
|
1526
|
+
|
|
1527
|
+
## Understanding "auto" vs Omitting Props
|
|
1528
|
+
|
|
1529
|
+
**Key Concept:** In ASCIITORIUM, omitting `width` or `height` props is the same as setting them to `undefined` or `"auto"`.
|
|
1530
|
+
|
|
1531
|
+
- `width={40}` → Fixed width of 40 characters
|
|
1532
|
+
- `width="fill"` → Fill available parent space
|
|
1533
|
+
- `width="auto"` → Auto-size to content (same as omitting the prop)
|
|
1534
|
+
- _Omitting width prop_ → Auto-size to content (recommended approach)
|
|
1535
|
+
|
|
1536
|
+
**Recommendation:** Omit props instead of using `"auto"` for clarity. The type system allows `"auto"` but it's unnecessary.
|
|
1537
|
+
|
|
1538
|
+
**Component-specific auto-sizing:**
|
|
1539
|
+
|
|
1540
|
+
- **Text**: Auto-sizes to content length (width) and wrapped lines (height) when props omitted
|
|
1541
|
+
- **Button**: Always calculates size based on content (`buttonText.length + 7` for width, `4` for height)
|
|
1542
|
+
- **Art**: Auto-sizes to loaded art dimensions when props omitted
|
|
1543
|
+
- **Column**: Auto-sizes to fit children when width omitted
|
|
1544
|
+
- **Row**: Defaults to `width="fill"`
|
|
1545
|
+
|
|
1546
|
+
---
|
|
1547
|
+
|
|
1548
|
+
## Tips for LLMs
|
|
1549
|
+
|
|
1550
|
+
1. **Always use `new State()` for reactive values** - Never use React's useState
|
|
1551
|
+
2. **State values are accessed via `.value`** - Read and write using `.value`
|
|
1552
|
+
3. **Text newlines use `¶` (pilcrow)** - Not `\n`
|
|
1553
|
+
4. **Components accept children via JSX or explicit props** - Both patterns work
|
|
1554
|
+
5. **GameWorld must be awaited** - `await gameWorld.ready` before rendering
|
|
1555
|
+
6. **Keybinds are invisible components** - They never render, just register handlers
|
|
1556
|
+
7. **MapView and FirstPersonView are display-only** - Movement logic goes in GameWorld or Keybinds
|
|
1557
|
+
8. **Legend system drives collision** - Use `isSolid()` for movement validation
|
|
1558
|
+
9. **No CSS or className** - ASCIITORIUM uses ASCII rendering, not DOM styling
|
|
1559
|
+
10. **Omit width/height props for auto-sizing** - Components have smart defaults (Text/Art/Column size to content, Button calculates from label, Row fills width)
|
|
1560
|
+
|
|
1561
|
+
---
|
|
1562
|
+
|
|
1563
|
+
_End of Reference_
|