create-asciitorium 0.1.25 → 0.1.27
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 +17 -1
- package/dist/templates/base/index.html +1 -1
- package/dist/templates/base/public/art/asciitorium.txt +7 -7
- package/dist/templates/base/public/art/borders/bubbles.txt +16 -0
- package/dist/templates/base/public/art/borders/dna.txt +9 -0
- package/dist/templates/base/public/art/mazes/example.txt +21 -0
- package/dist/templates/base/public/images/logo.png +0 -0
- package/dist/templates/base/scripts/maze-builder.js +294 -0
- package/dist/templates/base/src/main.tsx +11 -14
- package/package.json +8 -1
- package/dist/templates/base/src/examples/AsciiArtExample.tsx +0 -9
- package/dist/templates/base/src/examples/ButtonExample.tsx +0 -21
- package/dist/templates/base/src/examples/LayoutExample.tsx +0 -45
- package/dist/templates/base/src/examples/MultiSelectExample.tsx +0 -51
- package/dist/templates/base/src/examples/ProgressBarExample.tsx +0 -42
- package/dist/templates/base/src/examples/SelectExample.tsx +0 -40
- package/dist/templates/base/src/examples/TabsExample.tsx +0 -54
- package/dist/templates/base/src/examples/TextExample.tsx +0 -24
- package/dist/templates/base/src/examples/TextInputExample.tsx +0 -25
package/README.md
CHANGED
|
@@ -53,6 +53,18 @@ List available FIGlet fonts:
|
|
|
53
53
|
npm run figlet:fonts
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
Generate ASCII maze files (placed in public/art/mazes):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node scripts/maze-builder.js <width> <height> <filename> [--smooth]
|
|
60
|
+
|
|
61
|
+
# Examples:
|
|
62
|
+
node scripts/maze-builder.js 10 10 dungeon-level-1.txt
|
|
63
|
+
node scripts/maze-builder.js 15 20 castle-maze.txt --smooth
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `--smooth` flag uses Unicode box drawing characters for improved visual appearance.
|
|
67
|
+
|
|
56
68
|
## Generated Project Structure
|
|
57
69
|
|
|
58
70
|
A typical generated project will look like:
|
|
@@ -60,8 +72,12 @@ A typical generated project will look like:
|
|
|
60
72
|
```
|
|
61
73
|
my-asciitorium-app/
|
|
62
74
|
├── public/
|
|
63
|
-
│ ├── art/
|
|
75
|
+
│ ├── art/
|
|
76
|
+
│ │ └── mazes/ # Generated maze files
|
|
64
77
|
│ └── fonts/ # Custom fonts
|
|
78
|
+
├── scripts/
|
|
79
|
+
│ ├── gen-figlet-art.js # FIGlet art generator
|
|
80
|
+
│ └── maze-builder.js # ASCII maze generator
|
|
65
81
|
├── src/
|
|
66
82
|
│ ├── main.tsx # Main application entry
|
|
67
83
|
│ └── vite-env.d.ts # TypeScript definitions
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/png" href="/images/logo.png" />
|
|
5
6
|
<title>asciitorium</title>
|
|
6
|
-
<link rel="icon" type="image/png" href="/logo.png" />
|
|
7
7
|
<link rel="stylesheet" href="index.css" />
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
@@ -2,37 +2,37 @@
|
|
|
2
2
|
_ _ _ _
|
|
3
3
|
__ _ ___ ___(_|_) |_ ___ _ __(_)_ _ _ __ ___
|
|
4
4
|
╱ _` ╱ __|╱ __| | | __╱ _ ╲| '__| | | | | '_ ` _ ╲
|
|
5
|
-
|
|
5
|
+
| (_| ╲__ ╲ (__| | | || (_) | | | | |_| | | | | | ⎸
|
|
6
6
|
╲__,_|___╱╲___|_|_|╲__╲___╱|_| |_|╲__,_|_| |_| |_⎸
|
|
7
7
|
¶ {"duration":8000}
|
|
8
8
|
_ _ _
|
|
9
9
|
__ _ ___ ___(_| )/|_ ___ _ __(_)_ _ _ __ .
|
|
10
10
|
╱y_` ╱ __|╱\__| |\| __╱ _/╲| '__|/| |. \| '_ ` _/╲.
|
|
11
|
-
|
|
11
|
+
| (_| ╲__ ╲ (__|/| | || (_) |/| | | |_| | | | | | ⎸
|
|
12
12
|
╲__,_|__\╱╲___|_|_|╲__╲_y_╱|_| |_|╲__,_|_| |y| |_⎸
|
|
13
13
|
¶ {"duration":1000}
|
|
14
14
|
. _ * _ * .
|
|
15
15
|
__ _ ___ ___(_|_).|_ * _ __(_)_ _ * __
|
|
16
16
|
╱ _` ╱ __|╱ _ . | __╱ . ╲ '__| | | | ' ` ╲ .
|
|
17
|
-
|
|
17
|
+
| (_| ╲__ (__| *|.| || (_) | * | | |_| *| | | ⎸ .
|
|
18
18
|
╲__,_|___╱╲__ ._|_|╲__╲ * ╱|_| .|╲__ |_| |_⎸
|
|
19
19
|
¶ {"duration":140}
|
|
20
20
|
* _ * . * . *
|
|
21
21
|
__ * (_|_) . * _ __ . . __
|
|
22
22
|
╱ ` __| | __╱ * ╲ '__| * ` ╲
|
|
23
|
-
|
|
23
|
+
| * * ╲__ | | || (_) | . | * * | | ⎸
|
|
24
24
|
. .|___╱ |_| ╲__╲___╱|_| . . |_| . |_⎸
|
|
25
25
|
¶ {"duration":140}
|
|
26
26
|
. * . _ * . . .
|
|
27
27
|
__ * * (_|_). . * _ * . __
|
|
28
28
|
╱ _` * __| | __╱ * * ╲ '__| . * _ ╲
|
|
29
|
-
|
|
29
|
+
| (_| * ╲__ | || (_) . | * . * | | ⎸
|
|
30
30
|
. ._|___╱ |_| ╲__╲ * |_| . * |_| |_⎸
|
|
31
31
|
¶ {"duration":160}
|
|
32
32
|
. * . _ * . * . .
|
|
33
33
|
* . (_|_) . . * . * .
|
|
34
34
|
╱ ` * __| __╱ * . * . * . ╲
|
|
35
|
-
|
|
35
|
+
| * * ╲__ | (_)* . * . * . | ⎸
|
|
36
36
|
. .|___╱ |_| * . * . . |_|
|
|
37
37
|
¶ {"duration":140}
|
|
38
38
|
. * . . * . .
|
|
@@ -108,4 +108,4 @@
|
|
|
108
108
|
_ _ _ _
|
|
109
109
|
__ _ ___ ___(_|_) |_ ___ _ __(_)_ _ _ __ ___
|
|
110
110
|
╱ _` ╱ __|╱ __| | | __╱ _ ╲| '__| | | | | '_ ` _ ╲
|
|
111
|
-
|
|
111
|
+
| (_| ╲__ ╲ (__| | | || (_) | | | | |_| | | | | | ⎸
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
╭───────────────────────────────────┬───╮
|
|
2
|
+
│ │ │
|
|
3
|
+
│ ╷ ╭───────────────┬───────╮ ╰─o─┤
|
|
4
|
+
│ │ │ │ │ │
|
|
5
|
+
│ │ ╵ ╶───────╮ ├─o─╮ ╰───╮ │
|
|
6
|
+
│ │ │ │ │ │ │
|
|
7
|
+
│ ╰───────┬───────╯ ├───┤ ╭───╯ │
|
|
8
|
+
│ │ │ │ │ │
|
|
9
|
+
├───────────╯ ╭───────┴─o─┤ │ ╶───┤
|
|
10
|
+
│ │ │ │ │
|
|
11
|
+
│ ╭───╴ ╭───╯ ╭───────╯ ├───╮ │
|
|
12
|
+
│ │ │ │ │ │ │
|
|
13
|
+
│ ╰───┬─o─┤ ╭───┤ ╶───╮ ├─o─┤ │
|
|
14
|
+
│ │ │ │ │ │ │ │ │
|
|
15
|
+
├───╮ ╰───╯ ├─o─╯ ╷ │ ╵ │ │
|
|
16
|
+
│ │ │ │ │ │ │
|
|
17
|
+
├─o─┴───────────┤ ╭───╯ ├───────┤ │
|
|
18
|
+
│ │ │ │ │ │
|
|
19
|
+
│ ╶───╮ ╶───╯ │ ╶───╯ ╷ ╵ │
|
|
20
|
+
│ │ │ │ │
|
|
21
|
+
╰───────┴───────────┴───────────┴───────╯
|
|
Binary file
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import fs from 'fs/promises'
|
|
2
|
+
import process from 'process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
function parseArgs() {
|
|
6
|
+
const args = process.argv.slice(2)
|
|
7
|
+
|
|
8
|
+
// Check for flags
|
|
9
|
+
let smooth = false
|
|
10
|
+
let filteredArgs = []
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
if (args[i] === '--smooth' || args[i] === '--unicode') {
|
|
14
|
+
smooth = true
|
|
15
|
+
} else {
|
|
16
|
+
filteredArgs.push(args[i])
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (filteredArgs.length !== 3) {
|
|
21
|
+
console.error('Usage: node maze-builder.js <width> <height> <filename> [--smooth]')
|
|
22
|
+
console.error('Example: node maze-builder.js 10 10 dungeon-level-1.txt --smooth')
|
|
23
|
+
console.error('Mazes will be saved to public/art/mazes/ directory')
|
|
24
|
+
console.error('Use --smooth flag to enable Unicode box drawing characters')
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const width = parseInt(filteredArgs[0])
|
|
29
|
+
const height = parseInt(filteredArgs[1])
|
|
30
|
+
const filename = filteredArgs[2]
|
|
31
|
+
|
|
32
|
+
if (isNaN(width) || width <= 0) {
|
|
33
|
+
console.error('Width must be a positive integer')
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (isNaN(height) || height <= 0) {
|
|
38
|
+
console.error('Height must be a positive integer')
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Construct the full path to the mazes directory
|
|
43
|
+
const mazesDir = path.join(process.cwd(), '../public/art/mazes')
|
|
44
|
+
const fullPath = path.join(mazesDir, filename)
|
|
45
|
+
|
|
46
|
+
return { width, height, filename: fullPath, mazesDir, smooth }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class MazeBuilder {
|
|
50
|
+
// Maze is a 2D array of chars.
|
|
51
|
+
// example 10x10 grid = [
|
|
52
|
+
// 0123456789012345678
|
|
53
|
+
// 0'+-0-+---+---+---+---+',
|
|
54
|
+
// 1'0 1 2 3 4 5 6 7 8 9 |',
|
|
55
|
+
// 2'+-2-+---+---+---+---+',
|
|
56
|
+
// 3'| 3 | | |',
|
|
57
|
+
// 4'+-4-+ + + + +',
|
|
58
|
+
// 5'| 5 | | | |',
|
|
59
|
+
// 6'+-6-+ +---+ + +',
|
|
60
|
+
// 7'| 7 | | | |',
|
|
61
|
+
// 8'+-8-+ + +---+ +',
|
|
62
|
+
// 9'| 9 | | |',
|
|
63
|
+
// '+---+---+---+---+---+',
|
|
64
|
+
// ]
|
|
65
|
+
// is actually a 11x19 2d array
|
|
66
|
+
// conversion: y = y, x = x*2
|
|
67
|
+
// only place items and creatures on odd values of y and x
|
|
68
|
+
|
|
69
|
+
constructor(width, height, fileName, mazesDir, smooth = false) {
|
|
70
|
+
this.fileName = fileName
|
|
71
|
+
this.mazesDir = mazesDir
|
|
72
|
+
this.width = width
|
|
73
|
+
this.height = height
|
|
74
|
+
this.smooth = smooth
|
|
75
|
+
this.map = []
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async build() {
|
|
79
|
+
console.log(`--- building maze (${this.width}x${this.height}) ---`)
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
this.map = this.createMaze()
|
|
83
|
+
console.log(' - created base maze')
|
|
84
|
+
|
|
85
|
+
// Add rooms
|
|
86
|
+
this.addRooms()
|
|
87
|
+
console.log(' - added rooms')
|
|
88
|
+
|
|
89
|
+
// Smooth the maze with Unicode box drawing characters (if requested)
|
|
90
|
+
if (this.smooth) {
|
|
91
|
+
this.mapUpdate(this.map)
|
|
92
|
+
console.log(' - smoothed maze appearance with Unicode characters')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Save the maze to text file
|
|
96
|
+
await this.saveToText()
|
|
97
|
+
console.log('--- maze build complete ---')
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(`Error building maze: ${error.message}`)
|
|
100
|
+
console.error(error)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async saveToText() {
|
|
105
|
+
try {
|
|
106
|
+
// Ensure the mazes directory exists
|
|
107
|
+
await fs.mkdir(this.mazesDir, { recursive: true })
|
|
108
|
+
|
|
109
|
+
// Join the map array with newlines to create plain text
|
|
110
|
+
const textOutput = this.map.join('\n')
|
|
111
|
+
|
|
112
|
+
await fs.writeFile(this.fileName, textOutput, 'utf-8')
|
|
113
|
+
console.log(` - successfully wrote maze data to ${this.fileName}`)
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(`Error writing text to file: ${error.message}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
xOffset(x) {
|
|
120
|
+
return x * 4 + 2
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
yOffset(y) {
|
|
124
|
+
return y * 2 + 1
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Doors are placed at all dead ends with openings to the north and south.
|
|
128
|
+
addRooms() {
|
|
129
|
+
for (let y = 0; y < this.height; y++) {
|
|
130
|
+
const yPos = this.yOffset(y)
|
|
131
|
+
for (let x = 0; x < this.width; x++) {
|
|
132
|
+
const xPos = this.xOffset(x)
|
|
133
|
+
this.replaceSpacesWithDoors(xPos, yPos)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// identify dead ends to the north or south.
|
|
139
|
+
// If there is a dead end to the north or south, then specify the x and y position of the opening.
|
|
140
|
+
replaceSpacesWithDoors(xPos, yPos) {
|
|
141
|
+
if (this.map[yPos][xPos] != ' ') return
|
|
142
|
+
const wallDirections = []
|
|
143
|
+
if (this.map[yPos][xPos - 2] != ' ' && this.map[yPos][xPos - 2] != 'o') wallDirections.push('west')
|
|
144
|
+
if (this.map[yPos][xPos + 2] != ' ' && this.map[yPos][xPos + 2] != 'o') wallDirections.push('east')
|
|
145
|
+
if (this.map[yPos - 1][xPos] != ' ' && this.map[yPos - 1][xPos] != 'o') wallDirections.push('north')
|
|
146
|
+
if (this.map[yPos + 1][xPos] != ' ' && this.map[yPos + 1][xPos] != 'o') wallDirections.push('south')
|
|
147
|
+
if (wallDirections.length == 3) {
|
|
148
|
+
if (wallDirections.includes('north') && !wallDirections.includes('south')) {
|
|
149
|
+
this.map[yPos + 1] = this.replaceAt(this.map[yPos + 1], xPos - 1, '-')
|
|
150
|
+
this.map[yPos + 1] = this.replaceAt(this.map[yPos + 1], xPos, 'o')
|
|
151
|
+
this.map[yPos + 1] = this.replaceAt(this.map[yPos + 1], xPos + 1, '-')
|
|
152
|
+
} else if (!wallDirections.includes('north') && wallDirections.includes('south')) {
|
|
153
|
+
this.map[yPos - 1] = this.replaceAt(this.map[yPos - 1], xPos - 1, '-')
|
|
154
|
+
this.map[yPos - 1] = this.replaceAt(this.map[yPos - 1], xPos, 'o')
|
|
155
|
+
this.map[yPos - 1] = this.replaceAt(this.map[yPos - 1], xPos + 1, '-')
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
replaceAt(string, index, replacement) {
|
|
161
|
+
return string.substr(0, index) + replacement + string.substr(index + replacement.length)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
mapUpdate(map) {
|
|
165
|
+
const copy = [...map]
|
|
166
|
+
for (let y = 0; y < copy.length; y++) {
|
|
167
|
+
for (let x = 0; x < copy[y].length; x++) {
|
|
168
|
+
if (copy[y][x] === '-') copy[y] = copy[y].substring(0, x) + '─' + copy[y].substring(x + 1)
|
|
169
|
+
if (copy[y][x] === '|') copy[y] = copy[y].substring(0, x) + '│' + copy[y].substring(x + 1)
|
|
170
|
+
if (copy[y][x] === '+') {
|
|
171
|
+
let north = y > 0 && copy[y - 1][x] !== ' '
|
|
172
|
+
let south = y < copy.length - 1 && copy[y + 1][x] !== ' '
|
|
173
|
+
let west = x > 0 && copy[y][x - 1] !== ' '
|
|
174
|
+
let east = x < copy[y].length - 1 && copy[y][x + 1] !== ' '
|
|
175
|
+
if (north && south && west && east) copy[y] = copy[y].substring(0, x) + '┼' + copy[y].substring(x + 1)
|
|
176
|
+
else if (north && south && west) copy[y] = copy[y].substring(0, x) + '┤' + copy[y].substring(x + 1)
|
|
177
|
+
else if (north && south && east) copy[y] = copy[y].substring(0, x) + '├' + copy[y].substring(x + 1)
|
|
178
|
+
else if (north && east && west) copy[y] = copy[y].substring(0, x) + '┴' + copy[y].substring(x + 1)
|
|
179
|
+
else if (south && east && west) copy[y] = copy[y].substring(0, x) + '┬' + copy[y].substring(x + 1)
|
|
180
|
+
else if (north && south) copy[y] = copy[y].substring(0, x) + '│' + copy[y].substring(x + 1)
|
|
181
|
+
else if (west && east) copy[y] = copy[y].substring(0, x) + '─' + copy[y].substring(x + 1)
|
|
182
|
+
else if (north && east) copy[y] = copy[y].substring(0, x) + '╰' + copy[y].substring(x + 1)
|
|
183
|
+
else if (north && west) copy[y] = copy[y].substring(0, x) + '╯' + copy[y].substring(x + 1)
|
|
184
|
+
else if (south && east) copy[y] = copy[y].substring(0, x) + '╭' + copy[y].substring(x + 1)
|
|
185
|
+
else if (south && west) copy[y] = copy[y].substring(0, x) + '╮' + copy[y].substring(x + 1)
|
|
186
|
+
else if (east) copy[y] = copy[y].substring(0, x) + '╶' + copy[y].substring(x + 1)
|
|
187
|
+
else if (west) copy[y] = copy[y].substring(0, x) + '╴' + copy[y].substring(x + 1)
|
|
188
|
+
else if (north) copy[y] = copy[y].substring(0, x) + '╵' + copy[y].substring(x + 1)
|
|
189
|
+
else if (south) copy[y] = copy[y].substring(0, x) + '╷' + copy[y].substring(x + 1)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this.map = copy
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
createMaze() {
|
|
197
|
+
// Establish variables and starting grid
|
|
198
|
+
const totalCells = this.height * this.width
|
|
199
|
+
const unvisited = [] // cells that are unvisited.
|
|
200
|
+
const maze = []
|
|
201
|
+
for (let y = 0; y < this.height; y++) {
|
|
202
|
+
unvisited[y] = []
|
|
203
|
+
maze[y * 2] = ''
|
|
204
|
+
maze[y * 2 + 1] = ''
|
|
205
|
+
for (let x = 0; x < this.width; x++) {
|
|
206
|
+
maze[y * 2] += '+---'
|
|
207
|
+
maze[y * 2 + 1] += '| '
|
|
208
|
+
unvisited[y][x] = true
|
|
209
|
+
}
|
|
210
|
+
maze[y * 2] += '+'
|
|
211
|
+
maze[y * 2 + 1] += '|'
|
|
212
|
+
}
|
|
213
|
+
maze[this.height * 2] = ''
|
|
214
|
+
for (let x = 0; x < this.width; x++) {
|
|
215
|
+
maze[this.height * 2] += '+---'
|
|
216
|
+
}
|
|
217
|
+
maze[this.height * 2] += '+'
|
|
218
|
+
|
|
219
|
+
// Set a random position to start from
|
|
220
|
+
let current = [Math.floor(Math.random() * this.height), Math.floor(Math.random() * this.width)]
|
|
221
|
+
const path = [current]
|
|
222
|
+
unvisited[current[0]][current[1]] = false
|
|
223
|
+
let visited = 1
|
|
224
|
+
|
|
225
|
+
// // Loop through all available cell positions (given walls take up half the cells, we divide the total by two)
|
|
226
|
+
while (visited < totalCells) {
|
|
227
|
+
// Determine neighboring cells (0,1) and pathways to them (2,3)
|
|
228
|
+
const south = [current[0] - 1, current[1], 'north']
|
|
229
|
+
const north = [current[0] + 1, current[1], 'south']
|
|
230
|
+
const east = [current[0], current[1] - 1, 'west']
|
|
231
|
+
const west = [current[0], current[1] + 1, 'east']
|
|
232
|
+
const possibleNeighbors = [south, north, east, west]
|
|
233
|
+
const neighbors = []
|
|
234
|
+
|
|
235
|
+
// Determine if each neighboring cell is in game grid, and whether it has already been checked
|
|
236
|
+
for (let i = 0; i < possibleNeighbors.length; i++) {
|
|
237
|
+
if (
|
|
238
|
+
possibleNeighbors[i][0] > -1 &&
|
|
239
|
+
possibleNeighbors[i][0] < this.height &&
|
|
240
|
+
possibleNeighbors[i][1] > -1 &&
|
|
241
|
+
possibleNeighbors[i][1] < this.width &&
|
|
242
|
+
unvisited[possibleNeighbors[i][0]][possibleNeighbors[i][1]] == true
|
|
243
|
+
) {
|
|
244
|
+
neighbors.push(possibleNeighbors[i])
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If at least one active neighboring cell has been found
|
|
249
|
+
if (neighbors.length > 0) {
|
|
250
|
+
// Choose one of the neighbors at random
|
|
251
|
+
const next = neighbors[Math.floor(Math.random() * neighbors.length)]
|
|
252
|
+
|
|
253
|
+
// Remove the wall between the current cell and the chosen neighboring cell in the maze view.
|
|
254
|
+
// split maze
|
|
255
|
+
let xBorderToRemove = 0
|
|
256
|
+
let yBorderToRemove = 0
|
|
257
|
+
if (next[2] == 'south') {
|
|
258
|
+
yBorderToRemove = this.yOffset(next[0]) - 1
|
|
259
|
+
xBorderToRemove = this.xOffset(next[1])
|
|
260
|
+
maze[yBorderToRemove] = this.replaceAt(maze[yBorderToRemove], xBorderToRemove - 1, ' ')
|
|
261
|
+
maze[yBorderToRemove] = this.replaceAt(maze[yBorderToRemove], xBorderToRemove + 1, ' ')
|
|
262
|
+
} else if (next[2] == 'north') {
|
|
263
|
+
yBorderToRemove = this.yOffset(next[0]) + 1
|
|
264
|
+
xBorderToRemove = this.xOffset(next[1])
|
|
265
|
+
maze[yBorderToRemove] = this.replaceAt(maze[yBorderToRemove], xBorderToRemove - 1, ' ')
|
|
266
|
+
maze[yBorderToRemove] = this.replaceAt(maze[yBorderToRemove], xBorderToRemove + 1, ' ')
|
|
267
|
+
} else if (next[2] == 'east') {
|
|
268
|
+
yBorderToRemove = this.yOffset(next[0])
|
|
269
|
+
xBorderToRemove = this.xOffset(next[1]) - 2
|
|
270
|
+
} else if (next[2] == 'west') {
|
|
271
|
+
yBorderToRemove = this.yOffset(next[0])
|
|
272
|
+
xBorderToRemove = this.xOffset(next[1]) + 2
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
maze[yBorderToRemove] = this.replaceAt(maze[yBorderToRemove], xBorderToRemove, ' ')
|
|
276
|
+
|
|
277
|
+
// Mark the neighbor as visited, and set it as the current cell
|
|
278
|
+
current = next
|
|
279
|
+
unvisited[current[0]][current[1]] = false
|
|
280
|
+
visited++
|
|
281
|
+
path.push(current)
|
|
282
|
+
}
|
|
283
|
+
// Otherwise go back up a step and keep going
|
|
284
|
+
else {
|
|
285
|
+
current = path.pop()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return maze
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { width, height, filename, mazesDir, smooth } = parseArgs()
|
|
293
|
+
const builder = new MazeBuilder(width, height, filename, mazesDir, smooth)
|
|
294
|
+
builder.build()
|
|
@@ -11,14 +11,13 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
ButtonExample,
|
|
13
13
|
CelticBorderExample,
|
|
14
|
-
SelectExample,
|
|
15
14
|
MultiSelectExample,
|
|
16
15
|
RelativeSizingExample,
|
|
17
|
-
TextInputExample,
|
|
18
16
|
TabsExample,
|
|
19
|
-
TextExample,
|
|
20
17
|
AsciiArtExample,
|
|
21
18
|
SlidersExample,
|
|
19
|
+
FormExample,
|
|
20
|
+
AsciiMazeExample,
|
|
22
21
|
} from 'asciitorium/examples';
|
|
23
22
|
|
|
24
23
|
// Load the title ASCII art
|
|
@@ -33,46 +32,44 @@ const selectedComponent = new PersistentState(
|
|
|
33
32
|
// Component list for navigation
|
|
34
33
|
const componentList = [
|
|
35
34
|
'AsciiArt',
|
|
35
|
+
'AsciiMaze',
|
|
36
36
|
'Button',
|
|
37
37
|
'CelticBorder',
|
|
38
|
+
'Form',
|
|
38
39
|
'MultiSelect',
|
|
39
40
|
'RelativeSizing',
|
|
40
|
-
'Select',
|
|
41
41
|
'Sliders',
|
|
42
42
|
'Tabs',
|
|
43
|
-
'Text',
|
|
44
|
-
'TextInput',
|
|
45
43
|
];
|
|
46
44
|
|
|
47
45
|
// Component mapping for dynamic content
|
|
48
46
|
const examples = {
|
|
47
|
+
AsciiArt: AsciiArtExample,
|
|
48
|
+
AsciiMaze: AsciiMazeExample,
|
|
49
49
|
Button: ButtonExample,
|
|
50
50
|
CelticBorder: CelticBorderExample,
|
|
51
|
-
Select: SelectExample,
|
|
52
51
|
MultiSelect: MultiSelectExample,
|
|
53
52
|
RelativeSizing: RelativeSizingExample,
|
|
54
|
-
TextInput: TextInputExample,
|
|
55
53
|
Sliders: SlidersExample,
|
|
54
|
+
Form: FormExample,
|
|
56
55
|
Tabs: TabsExample,
|
|
57
|
-
Text: TextExample,
|
|
58
|
-
AsciiArt: AsciiArtExample,
|
|
59
56
|
};
|
|
60
57
|
|
|
61
58
|
const app = (
|
|
62
59
|
<App>
|
|
63
60
|
<AsciiArt content={titleArt} align="center" gap={{ bottom: 2 }} />
|
|
64
|
-
<Row height="
|
|
61
|
+
<Row height="fill">
|
|
65
62
|
<Select
|
|
66
63
|
label="Components:"
|
|
67
64
|
width="30%"
|
|
68
|
-
height="
|
|
65
|
+
height="fill"
|
|
69
66
|
items={componentList}
|
|
70
67
|
selectedItem={selectedComponent}
|
|
71
68
|
border
|
|
72
69
|
/>
|
|
73
70
|
<Component
|
|
74
|
-
width="
|
|
75
|
-
height="
|
|
71
|
+
width="fill"
|
|
72
|
+
height="fill"
|
|
76
73
|
dynamicContent={{
|
|
77
74
|
selectedKey: selectedComponent,
|
|
78
75
|
componentMap: examples,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-asciitorium",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Scaffold a Vite + TypeScript project prewired for asciitorium (web + cli).",
|
|
6
6
|
"bin": {
|
|
@@ -38,5 +38,12 @@
|
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/tgruby/asciitorium.git"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/tgruby/asciitorium/issues"
|
|
41
48
|
}
|
|
42
49
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { AsciiArt, Box, loadArt } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
const computer = await loadArt('./art/computer.txt');
|
|
4
|
-
|
|
5
|
-
export const AsciiArtExample = () => (
|
|
6
|
-
<Box width={42} height={28} layout="aligned" label="ASCII Art Example:" border>
|
|
7
|
-
<AsciiArt content={computer} align="center" />
|
|
8
|
-
</Box>
|
|
9
|
-
);
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Text, State, Button, HR, Box } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const ButtonExample = () => {
|
|
4
|
-
// State for button click count - local to each component instance
|
|
5
|
-
const buttonClickCount = new State(0);
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<Box width={42} height={28} label="Button Example:" border>
|
|
9
|
-
<Button
|
|
10
|
-
label="I'm a Button!"
|
|
11
|
-
align="center"
|
|
12
|
-
onClick={() => (buttonClickCount.value = buttonClickCount.value + 1)}
|
|
13
|
-
gap={{top: 4, bottom: 3}}
|
|
14
|
-
/>
|
|
15
|
-
<Box layout="row" gap={{left: 6, right: 2}} align="center">
|
|
16
|
-
<Text>Click Count: </Text>
|
|
17
|
-
<Text width={4}>{buttonClickCount}</Text>
|
|
18
|
-
</Box>
|
|
19
|
-
</Box>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Text, HR, VR, Box, CelticBorder } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const LayoutExample = () => (
|
|
4
|
-
<Box width={42} height={27} border layout="column">
|
|
5
|
-
<Text
|
|
6
|
-
width={40}
|
|
7
|
-
height={2}
|
|
8
|
-
>
|
|
9
|
-
Layout Components - HR, VR, Box, Borders
|
|
10
|
-
</Text>
|
|
11
|
-
<HR gap={1} />
|
|
12
|
-
<Box
|
|
13
|
-
width={20}
|
|
14
|
-
height={5}
|
|
15
|
-
border
|
|
16
|
-
gap={1}
|
|
17
|
-
children={[
|
|
18
|
-
new Text({
|
|
19
|
-
width: 16,
|
|
20
|
-
height: 3,
|
|
21
|
-
children: ['Box with border\nand content'],
|
|
22
|
-
}),
|
|
23
|
-
]}
|
|
24
|
-
/>
|
|
25
|
-
<CelticBorder
|
|
26
|
-
width={20}
|
|
27
|
-
height={5}
|
|
28
|
-
gap={1}
|
|
29
|
-
children={[
|
|
30
|
-
new Text({
|
|
31
|
-
width: 16,
|
|
32
|
-
height: 3,
|
|
33
|
-
children: ['Celtic border\ndecoration'],
|
|
34
|
-
}),
|
|
35
|
-
]}
|
|
36
|
-
/>
|
|
37
|
-
<HR gap={1} />
|
|
38
|
-
<Text
|
|
39
|
-
width={35}
|
|
40
|
-
height={2}
|
|
41
|
-
>
|
|
42
|
-
Layout components help organize\nand structure your UI.
|
|
43
|
-
</Text>
|
|
44
|
-
</Box>
|
|
45
|
-
);
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Text, State, MultiSelect, Box, HR } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const MultiSelectExample = () => {
|
|
4
|
-
const multiValue = new State(['Suspension', 'Brakes']);
|
|
5
|
-
const displayText = new State(multiValue.value.join(', '));
|
|
6
|
-
|
|
7
|
-
multiValue.subscribe((newValue) => {
|
|
8
|
-
displayText.value = newValue.join(', ');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const carParts = [
|
|
12
|
-
'Engine',
|
|
13
|
-
'Transmission',
|
|
14
|
-
'Brakes',
|
|
15
|
-
'Suspension',
|
|
16
|
-
'Exhaust',
|
|
17
|
-
'Radiator',
|
|
18
|
-
'Battery',
|
|
19
|
-
'Alternator',
|
|
20
|
-
'Starter',
|
|
21
|
-
'Fuel Pump',
|
|
22
|
-
'Air Filter',
|
|
23
|
-
'Oil Filter',
|
|
24
|
-
'Spark Plugs',
|
|
25
|
-
'Tires',
|
|
26
|
-
'Wheels',
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<Box label="MultiSelect Example:" width={42} height={28} border>
|
|
31
|
-
<MultiSelect
|
|
32
|
-
label="Car Parts:"
|
|
33
|
-
align="center"
|
|
34
|
-
width={34}
|
|
35
|
-
height={10}
|
|
36
|
-
items={carParts}
|
|
37
|
-
selectedItems={multiValue}
|
|
38
|
-
gap={{ top: 2, bottom: 2 }}
|
|
39
|
-
/>
|
|
40
|
-
<Box align="center" layout="row">
|
|
41
|
-
<Text align="top">Selected: </Text>
|
|
42
|
-
<Text
|
|
43
|
-
width={24}
|
|
44
|
-
height={7}
|
|
45
|
-
align="top"
|
|
46
|
-
content={displayText}
|
|
47
|
-
/>
|
|
48
|
-
</Box>
|
|
49
|
-
</Box>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { Text, State, ProgressBar, Button, Box, HR } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const ProgressBarExample = () => {
|
|
4
|
-
// State for progress bar - local to each component instance
|
|
5
|
-
const progressValue = new State(25);
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<Box width={42} height={28} border label="ProgressBar Example:" >
|
|
9
|
-
<Text align="center" gap={{top: 2}}>With Percentage</Text>
|
|
10
|
-
<ProgressBar
|
|
11
|
-
width={35}
|
|
12
|
-
percent={progressValue}
|
|
13
|
-
align="center"
|
|
14
|
-
showPercentage
|
|
15
|
-
/>
|
|
16
|
-
<Text align="center" gap={{top: 2}}>Without Percentage</Text>
|
|
17
|
-
<ProgressBar
|
|
18
|
-
width={35}
|
|
19
|
-
percent={progressValue}
|
|
20
|
-
showPercentage={false}
|
|
21
|
-
align="center"
|
|
22
|
-
/>
|
|
23
|
-
|
|
24
|
-
<Box layout="horizontal" align="center" gap={{ top: 3 }}>
|
|
25
|
-
<Button
|
|
26
|
-
label="Increase"
|
|
27
|
-
onClick={() =>
|
|
28
|
-
(progressValue.value = Math.min(100, progressValue.value + 10))
|
|
29
|
-
}
|
|
30
|
-
gap={1}
|
|
31
|
-
/>
|
|
32
|
-
<Button
|
|
33
|
-
label="Decrease"
|
|
34
|
-
onClick={() =>
|
|
35
|
-
(progressValue.value = Math.max(0, progressValue.value - 10))
|
|
36
|
-
}
|
|
37
|
-
gap={1}
|
|
38
|
-
/>
|
|
39
|
-
</Box>
|
|
40
|
-
</Box>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Text, State, Select, Box } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const SelectExample = () => {
|
|
4
|
-
const selectValue = new State('Tesla Model S');
|
|
5
|
-
|
|
6
|
-
const carModels = [
|
|
7
|
-
'Tesla Model S',
|
|
8
|
-
'BMW M3',
|
|
9
|
-
'Audi A4',
|
|
10
|
-
'Mercedes C-Class',
|
|
11
|
-
'Honda Civic',
|
|
12
|
-
'Toyota Camry',
|
|
13
|
-
'Ford Mustang',
|
|
14
|
-
'Chevrolet Corvette',
|
|
15
|
-
'Porsche 911',
|
|
16
|
-
'Ferrari 488',
|
|
17
|
-
'Lamborghini Huracan',
|
|
18
|
-
'McLaren 720S',
|
|
19
|
-
'Nissan GTR',
|
|
20
|
-
'Subaru WRX',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<Box width={42} height={28} label="Select Example:" border>
|
|
25
|
-
<Box align="center" gap={{ top: 2, bottom: 2 }}>
|
|
26
|
-
<Select
|
|
27
|
-
label="Car's to Select:"
|
|
28
|
-
width={34}
|
|
29
|
-
height={10}
|
|
30
|
-
items={carModels}
|
|
31
|
-
selectedItem={selectValue}
|
|
32
|
-
/>
|
|
33
|
-
</Box>
|
|
34
|
-
<Box align="center" gap={{ left: 5 }} layout="row">
|
|
35
|
-
<Text>Car Selected: </Text>
|
|
36
|
-
<Text width={20}>{selectValue}</Text>
|
|
37
|
-
</Box>
|
|
38
|
-
</Box>
|
|
39
|
-
);
|
|
40
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Text, State, Tabs, Box } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const TabsExample = () => {
|
|
4
|
-
// State for tabs - local to each component instance
|
|
5
|
-
const tabsValue = new State('Tab 1');
|
|
6
|
-
|
|
7
|
-
// Dynamic content state based on selected tab
|
|
8
|
-
const tabContentState = new State('');
|
|
9
|
-
tabsValue.subscribe((tab) => {
|
|
10
|
-
switch (tab) {
|
|
11
|
-
case 'Tab 1':
|
|
12
|
-
tabContentState.value =
|
|
13
|
-
'Content for Tab 1\nThis shows dynamic content\nbased on selected tab.';
|
|
14
|
-
break;
|
|
15
|
-
case 'Tab 2':
|
|
16
|
-
tabContentState.value =
|
|
17
|
-
'Content for Tab 2\nDifferent content here!\nTabs make navigation easy.';
|
|
18
|
-
break;
|
|
19
|
-
case 'Tab 3':
|
|
20
|
-
tabContentState.value =
|
|
21
|
-
'Content for Tab 3\nYet another section.\nUse arrow keys to switch.';
|
|
22
|
-
break;
|
|
23
|
-
default:
|
|
24
|
-
tabContentState.value = 'Select a tab above';
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Initialize content
|
|
29
|
-
tabContentState.value =
|
|
30
|
-
tabsValue.value === 'Tab 1'
|
|
31
|
-
? 'Content for Tab 1\nThis shows dynamic content\nbased on selected tab.'
|
|
32
|
-
: tabsValue.value === 'Tab 2'
|
|
33
|
-
? 'Content for Tab 2\nDifferent content here!\nTabs make navigation easy.'
|
|
34
|
-
: 'Content for Tab 3\nYet another section.\nUse arrow keys to switch.';
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Box width={42} height={28} border label="Tabs Example:">
|
|
38
|
-
<Tabs
|
|
39
|
-
tabs={['Tab 1', 'Tab 2', 'Tab 3']}
|
|
40
|
-
align="center"
|
|
41
|
-
selectedTab={tabsValue}
|
|
42
|
-
gap={1}
|
|
43
|
-
/>
|
|
44
|
-
<Box
|
|
45
|
-
width={38}
|
|
46
|
-
height={8}
|
|
47
|
-
gap={1}
|
|
48
|
-
children={[
|
|
49
|
-
new Text({ width: 30, height: 6, children: [tabContentState] }),
|
|
50
|
-
]}
|
|
51
|
-
/>
|
|
52
|
-
</Box>
|
|
53
|
-
);
|
|
54
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Text, AsciiArt, Box } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const TextExample = () => (
|
|
4
|
-
<Box width={42} height={28} border label="Text Example:">
|
|
5
|
-
<Text
|
|
6
|
-
width={36}
|
|
7
|
-
height={8}
|
|
8
|
-
border
|
|
9
|
-
gap={1}
|
|
10
|
-
>
|
|
11
|
-
{'This is bordered text with:\n\n - multiple lines,\n - food for thought, and\n - some formatting.'}
|
|
12
|
-
</Text>
|
|
13
|
-
<Text
|
|
14
|
-
width={36}
|
|
15
|
-
height={7}
|
|
16
|
-
border
|
|
17
|
-
gap={1}
|
|
18
|
-
>
|
|
19
|
-
This is a long text that should wrap automatically when width and height are specified and the content exceeds the available space.
|
|
20
|
-
</Text>
|
|
21
|
-
|
|
22
|
-
<Text>Simple Text.</Text>
|
|
23
|
-
</Box>
|
|
24
|
-
);
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Text, State, TextInput, HR, Box } from 'asciitorium';
|
|
2
|
-
|
|
3
|
-
export const TextInputExample = () => {
|
|
4
|
-
// States for text input components - local to each component instance
|
|
5
|
-
const textInputValue = new State('Hello World');
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<Box width={42} height={28} label="TextInput Example:" border>
|
|
9
|
-
<TextInput
|
|
10
|
-
gap={{ top: 2 }}
|
|
11
|
-
width={20}
|
|
12
|
-
align="center"
|
|
13
|
-
value={textInputValue}
|
|
14
|
-
placeholder="Enter text here..."
|
|
15
|
-
/>
|
|
16
|
-
<Text width={20} align="center" height={3} gap={{ left: 5, bottom: 4 }}>
|
|
17
|
-
{textInputValue}
|
|
18
|
-
</Text>
|
|
19
|
-
|
|
20
|
-
<Text align="center" width={30} height={3}>
|
|
21
|
-
Use arrow keys to move cursor. Numeric input restricts to numbers only.
|
|
22
|
-
</Text>
|
|
23
|
-
</Box>
|
|
24
|
-
);
|
|
25
|
-
};
|