hascii 0.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.
package/Makefile ADDED
@@ -0,0 +1,19 @@
1
+ .PHONY: build check test pack publish publish-dry
2
+
3
+ build:
4
+ bun run build
5
+
6
+ check:
7
+ bun run check
8
+
9
+ test:
10
+ bun test
11
+
12
+ pack:
13
+ npm pack --dry-run
14
+
15
+ publish-dry:
16
+ npm publish --dry-run
17
+
18
+ publish:
19
+ npm publish
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # Hascii
2
+
3
+ YAML-based fantasy console for the terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g hascii
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Start TUI console
15
+ hascii
16
+
17
+ # Run a cartridge file
18
+ hascii game.yaml
19
+
20
+ # Open in editor
21
+ hascii -e game.yaml
22
+ ```
23
+
24
+ ## Controls
25
+
26
+ | Key | Action |
27
+ |-----|--------|
28
+ | Arrow keys / WASD | Move |
29
+ | Z / J | Action A (confirm) |
30
+ | X / K | Action B (cancel) |
31
+ | ESC | Exit / Back |
32
+
33
+ ## YAML Format
34
+
35
+ ```yaml
36
+ # yaml-language-server: $schema=hascii/hascii.schema.json
37
+
38
+ meta:
39
+ title: "GAME"
40
+ frame: 10
41
+ bg: 0
42
+
43
+ init:
44
+ x: 40
45
+ y: 10
46
+ score: 0
47
+
48
+ update:
49
+ - if: btn.left
50
+ set: { x: { sub: [x, 1] } }
51
+ - if: btn.right
52
+ set: { x: { add: [x, 1] } }
53
+
54
+ draw:
55
+ - cls:
56
+ - fill: 10
57
+ - rect: [x, y, 2, 1]
58
+ - stroke: 7
59
+ - print: [score, 0, 0]
60
+ ```
61
+
62
+ ### Expressions
63
+
64
+ | Expression | Description |
65
+ |------------|-------------|
66
+ | `{ add: [a, b] }` | Addition |
67
+ | `{ sub: [a, b] }` | Subtraction |
68
+ | `{ mul: [a, b] }` | Multiplication |
69
+ | `{ div: [a, b] }` | Division |
70
+ | `{ mod: [a, b] }` | Modulo |
71
+ | `{ min: [a, b] }` | Minimum |
72
+ | `{ max: [a, b] }` | Maximum |
73
+ | `{ rnd: [min, max] }` | Random integer |
74
+ | `{ abs: n }` | Absolute value |
75
+ | `{ floor: n }` | Floor |
76
+ | `{ eq: [a, b] }` | Equal (returns 0 or 1) |
77
+ | `{ lt: [a, b] }` | Less than |
78
+ | `{ gt: [a, b] }` | Greater than |
79
+ | `{ and: [a, b, ...] }` | Logical AND (variadic) |
80
+ | `{ or: [a, b, ...] }` | Logical OR (variadic) |
81
+ | `{ not: a }` | Logical NOT |
82
+ | `{ at: [arr, i] }` | Array access |
83
+ | `{ len: arr }` | Array length |
84
+
85
+ ### Statements
86
+
87
+ ```yaml
88
+ # Set variables
89
+ - set: { x: 10, y: 20 }
90
+
91
+ # Conditional (short form)
92
+ - if: condition
93
+ set: { x: 0 }
94
+
95
+ # Conditional (with then/else)
96
+ - if: condition
97
+ then:
98
+ - set: { x: 1 }
99
+ else:
100
+ - set: { x: 0 }
101
+
102
+ # Loop with index
103
+ - each: items
104
+ as: item
105
+ index: i
106
+ do:
107
+ - print: [item, 0, i]
108
+
109
+ # Array operations
110
+ - push: { to: arr, item: value }
111
+ - filter:
112
+ list: arr
113
+ as: item
114
+ cond: { gt: [item, 0] }
115
+ ```
116
+
117
+ ### Drawing
118
+
119
+ ```yaml
120
+ draw:
121
+ - cls: # Clear screen
122
+ - fill: 10 # Set fill color (0-15)
123
+ - stroke: 7 # Set stroke color (0-15)
124
+ - noFill: # Disable fill
125
+ - noStroke: # Disable stroke
126
+ - rect: [x, y, w, h] # Draw rectangle
127
+ - print: [text, x, y] # Print text
128
+ ```
129
+
130
+ ### Colors (0-15)
131
+
132
+ | Index | Color |
133
+ |-------|-------|
134
+ | 0 | Black |
135
+ | 1 | Dark Blue |
136
+ | 2 | Dark Green |
137
+ | 3 | Dark Cyan |
138
+ | 4 | Dark Red |
139
+ | 5 | Dark Magenta |
140
+ | 6 | Brown |
141
+ | 7 | Light Gray |
142
+ | 8 | Dark Gray |
143
+ | 9 | Blue |
144
+ | 10 | Green |
145
+ | 11 | Cyan |
146
+ | 12 | Red |
147
+ | 13 | Magenta |
148
+ | 14 | Yellow |
149
+ | 15 | White |
150
+
151
+ ### Input
152
+
153
+ | Variable | Description |
154
+ |----------|-------------|
155
+ | `btn.left` | Left arrow held |
156
+ | `btn.right` | Right arrow held |
157
+ | `btn.up` | Up arrow held |
158
+ | `btn.down` | Down arrow held |
159
+ | `btn.a` | Z key held |
160
+ | `btn.b` | X key held |
161
+ | `btnPressed.left` | Left arrow just pressed |
162
+ | `btnPressed.right` | Right arrow just pressed |
163
+ | `btnPressed.up` | Up arrow just pressed |
164
+ | `btnPressed.down` | Down arrow just pressed |
165
+ | `btnPressed.a` | Z key just pressed |
166
+ | `btnPressed.b` | X key just pressed |
167
+
168
+ ## Editor
169
+
170
+ The TUI console includes a built-in editor with tabs:
171
+
172
+ | Tab | Description |
173
+ |-----|-------------|
174
+ | CODE | Edit YAML source |
175
+ | SPRITE | 16x8 ASCII sprites |
176
+ | MAP | 16x16 tile map |
177
+ | SFX | Sound effects |
178
+ | TRACKER | 4-channel music tracker |
179
+ | PLAY | Preview game |
180
+ | DATA | Save data |
181
+ | QUIT | Exit editor |
182
+
183
+ ## License
184
+
185
+ MIT
@@ -0,0 +1,69 @@
1
+ # yaml-language-server: $schema=../hascii.schema.json
2
+ # Draw pixels with arrow keys
3
+
4
+ meta:
5
+ title: "DRAW"
6
+ frame: 11
7
+ bg: 8
8
+ art:
9
+ - "\x1b[100m \x1b[0m"
10
+ - "\x1b[100m \x1b[41m \x1b[43m \x1b[42m \x1b[46m \x1b[44m \x1b[45m \x1b[100m \x1b[0m"
11
+ - "\x1b[100m \x1b[45m \x1b[41m \x1b[43m \x1b[42m \x1b[46m \x1b[44m \x1b[100m \x1b[0m"
12
+ - "\x1b[100m \x1b[44m \x1b[45m \x1b[41m \x1b[43m \x1b[42m \x1b[46m \x1b[100m \x1b[0m"
13
+ - "\x1b[100m \x1b[0m"
14
+ description: "Draw pixels. Z and X change color."
15
+
16
+ init:
17
+ x: 40
18
+ y: 10
19
+ color: 10
20
+ # Drawing history: separate arrays for x, y, color
21
+ px: []
22
+ py: []
23
+ pc: []
24
+
25
+ update:
26
+ - if: btn.left
27
+ set: { x: { max: [0, { sub: [x, 1] }] } }
28
+ - if: btn.right
29
+ set: { x: { min: [79, { add: [x, 1] }] } }
30
+ - if: btn.up
31
+ set: { y: { max: [0, { sub: [y, 1] }] } }
32
+ - if: btn.down
33
+ set: { y: { min: [21, { add: [y, 1] }] } }
34
+
35
+ # Change color with Z/X buttons
36
+ - if: btnPressed.a
37
+ set: { color: { mod: [{ add: [color, 1] }, 16] } }
38
+ - if: btnPressed.b
39
+ set: { color: { mod: [{ add: [color, 15] }, 16] } }
40
+
41
+ # Record pixel when moving (using variadic or)
42
+ - if: { or: [btn.left, btn.right, btn.up, btn.down] }
43
+ then:
44
+ - push: { to: px, item: x }
45
+ - push: { to: py, item: y }
46
+ - push: { to: pc, item: color }
47
+
48
+ draw:
49
+ - fill: 0
50
+ - cls:
51
+ - noStroke:
52
+
53
+ # Draw all recorded pixels (using index option)
54
+ - each: px
55
+ as: pxi
56
+ index: i
57
+ do:
58
+ - fill: { at: [pc, i] }
59
+ - rect: [pxi, { at: [py, i] }, 1, 1]
60
+
61
+ # Draw cursor
62
+ - fill: color
63
+ - rect: [x, y, 1, 1]
64
+
65
+ # Color indicator
66
+ - stroke: 7
67
+ - print: ["Color:", 1, 0]
68
+ - fill: color
69
+ - rect: [8, 0, 2, 1]
@@ -0,0 +1,22 @@
1
+ # yaml-language-server: $schema=../hascii.schema.json
2
+ # The simplest example - just display text
3
+
4
+ meta:
5
+ title: "HELLO"
6
+ frame: 11
7
+ bg: 4
8
+ art:
9
+ - "\x1b[44m \x1b[93m*\x1b[44m \x1b[93m*\x1b[44m \x1b[0m"
10
+ - "\x1b[44m \x1b[96m~\x1b[44m \x1b[97m#\x1b[44m \x1b[96m~\x1b[44m \x1b[0m"
11
+ - "\x1b[44m \x1b[95m+\x1b[44m \x1b[0m"
12
+ - "\x1b[44m \x1b[96m~\x1b[44m \x1b[96m~\x1b[44m \x1b[0m"
13
+ - "\x1b[44m \x1b[93m*\x1b[44m \x1b[93m*\x1b[44m \x1b[0m"
14
+ description: "The simplest example."
15
+
16
+ init: {}
17
+
18
+ update: []
19
+
20
+ draw:
21
+ - stroke: 11
22
+ - print: ["Hello, Hascii!", 32, 10]
@@ -0,0 +1,68 @@
1
+ # yaml-language-server: $schema=../hascii.schema.json
2
+ meta:
3
+ title: MOVE
4
+ frame: 14
5
+ bg: 1
6
+ art:
7
+ - "\e[41m \e[93m^\e[41m \e[0m"
8
+ - "\e[41m \e[93m<\e[41m \e[46m##\e[41m \e[93m>\e[41m \e[0m"
9
+ - "\e[41m \e[93mv\e[41m \e[0m"
10
+ - "\e[41m \e[0m"
11
+ - "\e[41m \e[90m..........\e[41m \e[0m"
12
+ description: Move the box with arrow keys.
13
+ init:
14
+ x: 35
15
+ y: 8
16
+ update:
17
+ - if: btn.left
18
+ set:
19
+ x:
20
+ max:
21
+ - 0
22
+ - sub:
23
+ - x
24
+ - 1
25
+ - if: btn.right
26
+ set:
27
+ x:
28
+ min:
29
+ - 71
30
+ - add:
31
+ - x
32
+ - 1
33
+ - if: btn.up
34
+ set:
35
+ y:
36
+ max:
37
+ - 0
38
+ - sub:
39
+ - y
40
+ - 1
41
+ - if: btn.down
42
+ set:
43
+ y:
44
+ min:
45
+ - 17
46
+ - add:
47
+ - y
48
+ - 1
49
+ draw:
50
+ - fill: 0
51
+ - cls: null
52
+ - noFill: null
53
+ - stroke: 14
54
+ - strokeStyle: round
55
+ - rect:
56
+ - x
57
+ - y
58
+ - 9
59
+ - 5
60
+ - stroke: 7
61
+ - print:
62
+ - Move me!
63
+ - add:
64
+ - x
65
+ - 1
66
+ - add:
67
+ - y
68
+ - 2
@@ -0,0 +1,185 @@
1
+ # yaml-language-server: $schema=../hascii.schema.json
2
+ # Classic Pong game with AI opponent
3
+
4
+ meta:
5
+ title: "PONG"
6
+ frame: 12
7
+ bg: 4
8
+ art:
9
+ - "\x1b[44m+-----------+ \x1b[0m"
10
+ - "\x1b[44m|\x1b[97m#\x1b[44m \x1b[93mo\x1b[44m \x1b[91m#\x1b[44m| \x1b[0m"
11
+ - "\x1b[44m|\x1b[97m#\x1b[44m : \x1b[91m#\x1b[44m| \x1b[0m"
12
+ - "\x1b[44m|\x1b[97m#\x1b[44m \x1b[91m#\x1b[44m| \x1b[0m"
13
+ - "\x1b[44m+-----------+ \x1b[0m"
14
+ description: "Beat the CPU. First to 5 wins."
15
+
16
+ init:
17
+ # Player paddle (left)
18
+ py: 8
19
+ # CPU paddle (right)
20
+ cy: 8
21
+ # Ball
22
+ bx: 40
23
+ by: 10
24
+ dx: 1
25
+ dy: 1
26
+ # Scores
27
+ ps: 0
28
+ cs: 0
29
+ # Game state: 0=playing, 1=player won, 2=cpu won
30
+ st: 0
31
+ # Speed timer
32
+ t: 0
33
+
34
+ update:
35
+ # Restart on Z when game over
36
+ - if: { and: [st, btnPressed.a] }
37
+ set:
38
+ st: 0
39
+ ps: 0
40
+ cs: 0
41
+ bx: 40
42
+ by: 10
43
+ dx: 1
44
+ dy: 1
45
+
46
+ - if: { not: st }
47
+ then:
48
+ - set: { t: { add: [t, 1] } }
49
+
50
+ # Player paddle control
51
+ - if: btn.up
52
+ set: { py: { max: [1, { sub: [py, 1] }] } }
53
+ - if: btn.down
54
+ set: { py: { min: [16, { add: [py, 1] }] } }
55
+
56
+ # CPU AI: only react when ball is coming toward CPU (dx > 0)
57
+ # and ball is past center line (bx > 40)
58
+ - if: { and: [{ gt: [dx, 0] }, { gt: [bx, 40] }] }
59
+ then:
60
+ # Move toward ball position
61
+ - if: { lt: [{ add: [cy, 2] }, by] }
62
+ set: { cy: { min: [16, { add: [cy, 1] }] } }
63
+ - if: { gt: [{ add: [cy, 2] }, by] }
64
+ set: { cy: { max: [1, { sub: [cy, 1] }] } }
65
+
66
+ # When ball going away, slowly return to center
67
+ - if: { lt: [dx, 0] }
68
+ then:
69
+ - if: { eq: [{ mod: [t, 4] }, 0] }
70
+ then:
71
+ - if: { lt: [{ add: [cy, 2] }, 10] }
72
+ set: { cy: { add: [cy, 1] } }
73
+ - if: { gt: [{ add: [cy, 2] }, 10] }
74
+ set: { cy: { sub: [cy, 1] } }
75
+
76
+ # Ball movement
77
+ - set:
78
+ bx: { add: [bx, dx] }
79
+ by: { add: [by, dy] }
80
+
81
+ # Wall bounce (top/bottom)
82
+ - if: { lte: [by, 1] }
83
+ set: { dy: 1 }
84
+ - if: { gte: [by, 20] }
85
+ set: { dy: -1 }
86
+
87
+ # Player paddle hit (left) - angle depends on hit position
88
+ - if: { and: [{ lte: [bx, 5] }, { gte: [by, py] }, { lte: [by, { add: [py, 4] }] }] }
89
+ then:
90
+ - set: { dx: 1 }
91
+ # Top of paddle -> up, bottom -> down
92
+ - if: { lte: [by, { add: [py, 1] }] }
93
+ set: { dy: -1 }
94
+ - if: { gte: [by, { add: [py, 3] }] }
95
+ set: { dy: 1 }
96
+
97
+ # CPU paddle hit (right)
98
+ - if: { and: [{ gte: [bx, 74] }, { gte: [by, cy] }, { lte: [by, { add: [cy, 4] }] }] }
99
+ then:
100
+ - set: { dx: -1 }
101
+ - if: { lte: [by, { add: [cy, 1] }] }
102
+ set: { dy: -1 }
103
+ - if: { gte: [by, { add: [cy, 3] }] }
104
+ set: { dy: 1 }
105
+
106
+ # CPU scores (ball past left)
107
+ - if: { lt: [bx, 0] }
108
+ set:
109
+ cs: { add: [cs, 1] }
110
+ bx: 40
111
+ by: 10
112
+ dx: -1
113
+
114
+ # Player scores (ball past right)
115
+ - if: { gt: [bx, 79] }
116
+ set:
117
+ ps: { add: [ps, 1] }
118
+ bx: 40
119
+ by: 10
120
+ dx: 1
121
+
122
+ # Check win condition
123
+ - if: { gte: [ps, 5] }
124
+ set: { st: 1 }
125
+ - if: { gte: [cs, 5] }
126
+ set: { st: 2 }
127
+
128
+ draw:
129
+ - fill: 0
130
+ - cls:
131
+
132
+ # Border
133
+ - noFill:
134
+ - stroke: 8
135
+ - strokeStyle: light
136
+ - rect: [0, 0, 80, 22]
137
+
138
+ # Center line
139
+ - stroke: 8
140
+ - print: ["|", 39, 2]
141
+ - print: ["|", 39, 4]
142
+ - print: ["|", 39, 6]
143
+ - print: ["|", 39, 8]
144
+ - print: ["|", 39, 10]
145
+ - print: ["|", 39, 12]
146
+ - print: ["|", 39, 14]
147
+ - print: ["|", 39, 16]
148
+ - print: ["|", 39, 18]
149
+ - print: ["|", 39, 20]
150
+
151
+ # Score display
152
+ - stroke: 11
153
+ - print: [ps, 30, 1]
154
+ - stroke: 8
155
+ - print: ["-", 36, 1]
156
+ - stroke: 9
157
+ - print: [cs, 42, 1]
158
+
159
+ # Player paddle (left, yellow)
160
+ - fill: 14
161
+ - noStroke:
162
+ - rect: [2, py, 2, 5]
163
+
164
+ # CPU paddle (right, red)
165
+ - fill: 12
166
+ - rect: [76, cy, 2, 5]
167
+
168
+ # Ball
169
+ - fill: 15
170
+ - rect: [bx, by, 2, 1]
171
+
172
+ # Win/Lose message
173
+ - if: { eq: [st, 1] }
174
+ then:
175
+ - stroke: 10
176
+ - print: ["YOU WIN!", 35, 10]
177
+ - stroke: 7
178
+ - print: ["Press Z", 36, 12]
179
+
180
+ - if: { eq: [st, 2] }
181
+ then:
182
+ - stroke: 12
183
+ - print: ["YOU LOSE", 35, 10]
184
+ - stroke: 7
185
+ - print: ["Press Z", 36, 12]
@@ -0,0 +1,69 @@
1
+ # yaml-language-server: $schema=../hascii.schema.json
2
+ # Digital rain effect
3
+
4
+ meta:
5
+ title: "RAIN"
6
+ frame: 2
7
+ bg: 0
8
+ art:
9
+ - "\x1b[40m \x1b[92m|\x1b[40m \x1b[32m|\x1b[40m \x1b[92m|\x1b[40m \x1b[32m|\x1b[40m \x1b[92m|\x1b[40m \x1b[0m"
10
+ - "\x1b[40m \x1b[32m|\x1b[40m \x1b[92m|\x1b[40m \x1b[32m|\x1b[40m \x1b[92m|\x1b[40m \x1b[32m|\x1b[40m \x1b[0m"
11
+ - "\x1b[40m \x1b[32m.\x1b[40m \x1b[32m|\x1b[40m \x1b[32m.\x1b[40m \x1b[32m|\x1b[40m \x1b[32m.\x1b[40m \x1b[0m"
12
+ - "\x1b[40m \x1b[32m.\x1b[40m \x1b[32m.\x1b[40m \x1b[0m"
13
+ - "\x1b[40m \x1b[0m"
14
+ description: "Digital rain effect."
15
+
16
+ init:
17
+ t: 0
18
+ d1: 5
19
+ d2: 12
20
+ d3: 3
21
+ d4: 8
22
+ d5: 15
23
+ d6: 1
24
+ d7: 9
25
+ d8: 6
26
+
27
+ update:
28
+ - set:
29
+ t: { add: [t, 1] }
30
+ d1: { mod: [{ add: [d1, 1] }, 22] }
31
+ d2: { mod: [{ add: [d2, 1] }, 22] }
32
+ d3: { mod: [{ add: [d3, 1] }, 22] }
33
+ d4: { mod: [{ add: [d4, 1] }, 22] }
34
+ d5: { mod: [{ add: [d5, 1] }, 22] }
35
+ d6: { mod: [{ add: [d6, 1] }, 22] }
36
+ d7: { mod: [{ add: [d7, 1] }, 22] }
37
+ d8: { mod: [{ add: [d8, 1] }, 22] }
38
+
39
+ draw:
40
+ - fill: 0
41
+ - cls:
42
+ - noStroke:
43
+
44
+ # Rain drops (bright heads)
45
+ - fill: 10
46
+ - rect: [10, d1, 1, 1]
47
+ - rect: [25, d2, 1, 1]
48
+ - rect: [40, d3, 1, 1]
49
+ - rect: [55, d4, 1, 1]
50
+ - rect: [70, d5, 1, 1]
51
+ - rect: [18, d6, 1, 1]
52
+ - rect: [33, d7, 1, 1]
53
+ - rect: [62, d8, 1, 1]
54
+
55
+ # Trails (dimmer)
56
+ - fill: 2
57
+ - rect: [10, { sub: [d1, 1] }, 1, 1]
58
+ - rect: [25, { sub: [d2, 1] }, 1, 1]
59
+ - rect: [40, { sub: [d3, 1] }, 1, 1]
60
+ - rect: [55, { sub: [d4, 1] }, 1, 1]
61
+ - rect: [70, { sub: [d5, 1] }, 1, 1]
62
+ - rect: [18, { sub: [d6, 1] }, 1, 1]
63
+ - rect: [33, { sub: [d7, 1] }, 1, 1]
64
+ - rect: [62, { sub: [d8, 1] }, 1, 1]
65
+
66
+ - rect: [10, { sub: [d1, 2] }, 1, 1]
67
+ - rect: [25, { sub: [d2, 2] }, 1, 1]
68
+ - rect: [40, { sub: [d3, 2] }, 1, 1]
69
+ - rect: [55, { sub: [d4, 2] }, 1, 1]